Source code for tuiview.viewerwidget


"""
Viewer Widget. Allows display of images,
zooming and panning etc.
"""
# This file is part of 'TuiView' - a simple Raster viewer
# Copyright (C) 2012  Sam Gillingham
#
# 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import json
import numpy
from PyQt5.QtWidgets import QAbstractScrollArea, QRubberBand, QApplication
from PyQt5.QtGui import QPainter, QCursor, QPixmap, QPainterPath, QPen
from PyQt5.QtCore import Qt, QRect, QSize, QPoint, pyqtSignal

from . import viewererrors
from . import viewerlayers
from .viewertoolclasses import PolygonToolInfo, PolylineToolInfo

VIEWER_ZOOM_WHEEL_FRACTION = 0.1  # viewport increased/decreased by the fraction
# on zoom out/ zoom in with mouse wheel
MIN_SELECTION_SIZE_PX = 9  # minimum number of pixels (on display) for a 'valid' 
# (non-zero) selection


[docs]class QueryInfo(object): """ Container class for the information passed in the locationSelected signal. """ def __init__(self, easting, northing, column, row, long, lat, data, layer, modifiers): self.easting = easting self.northing = northing self.column = column self.row = row self.long = long self.lat = lat self.data = data self.layer = layer self.modifiers = modifiers
[docs]class GeolinkInfo(object): """ Container class for the information passed in the geolinkMove and geolinkQueryPoint signals. """ def __init__(self, senderid, easting, northing, metresperwinpix=0): self.senderid = senderid self.easting = easting self.northing = northing self.metresperwinpix = metresperwinpix
[docs] def toString(self): """ Return as json """ dict = {'easting': self.easting, 'northing': self.northing, 'metresperwinpix': self.metresperwinpix} return json.dumps(dict)
[docs] @staticmethod def fromString(jstring): """ Create an instance from json """ dict = json.loads(jstring) obj = GeolinkInfo(0, dict['easting'], dict['northing'], dict['metresperwinpix']) return obj
[docs]class ActiveToolChangedInfo(object): """ Container class for info pass in the activeToolChanged signal """ def __init__(self, newTool, senderid): self.newTool = newTool self.senderid = senderid
VIEWER_TOOL_NONE = 0 VIEWER_TOOL_ZOOMIN = 1 VIEWER_TOOL_ZOOMOUT = 2 VIEWER_TOOL_PAN = 3 VIEWER_TOOL_QUERY = 4 VIEWER_TOOL_POLYGON = 5 VIEWER_TOOL_POLYLINE = 6 VIEWER_TOOL_VECTORQUERY = 7
[docs]class ViewerWidget(QAbstractScrollArea): """ The main ViewerWidget class. Should be embeddable in other applications. See the open() function for loading images. """ # signals geolinkMove = pyqtSignal(GeolinkInfo, name='geolinkMove') "viewer moved, geolink the others" geolinkQueryPoint = pyqtSignal(GeolinkInfo, name='geolinkQueryPoint') "viewer queried" # can't use ViewerWidget - use base class instead layerAdded = pyqtSignal(QAbstractScrollArea, name='layerAdded') "layer added" showStatusMessage = pyqtSignal('QString', name='showStatusMessage') "show new status message" activeToolChanged = pyqtSignal(ActiveToolChangedInfo, name='activeToolChanged') "active tool has changed" polygonCollected = pyqtSignal(PolygonToolInfo, name='polygonCollected') "polygon has been collected" polylineCollected = pyqtSignal(PolylineToolInfo, name='polylineCollected') "line has been collected" vectorLocationSelected = pyqtSignal(list, viewerlayers.ViewerVectorLayer, name='vectorLocationSelected') "a location on a vector as been selected" locationSelected = pyqtSignal(QueryInfo, name='locationSelected') "a location on a raster as been selected" def __init__(self, parent): QAbstractScrollArea.__init__(self, parent) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) # tracking works awfully badly under X # turn off until we work out a workaround self.verticalScrollBar().setTracking(False) self.horizontalScrollBar().setTracking(False) self.layers = viewerlayers.LayerManager() self.paintPoint = QPoint() # normally 0,0 unless we are panning # when moving the scroll bars # events get fired that we wish to ignore self.suppressscrollevent = False # set the background color to black so window # is black when nothing loaded and when panning # new areas are initially black. self.setBackgroundColor(Qt.black) # to do with tools self.rubberBand = None self.panCursor = None self.panGrabCursor = None self.zoomInCursor = None self.zoomOutCursor = None self.queryCursor = None self.vectorQueryCursor = None self.polygonCursor = None self.activeTool = VIEWER_TOOL_NONE self.panOrigin = None self.toolPoints = None # for line and polygon tools - list of points self.toolPointsFinished = True # True if we finished collecting # with line and poly tools self.toolPen = QPen() # for drawing the toolPoints self.toolPen.setWidth(1) self.toolPen.setColor(Qt.yellow) self.toolPen.setDashPattern([5, 5, 5, 5]) # Define the scroll wheel behaviour self.mouseWheelZoom = True # do we follow extent when geolinking? self.geolinkFollowExtent = True # to we query all layers or only displayed? self.queryOnlyDisplayed = False
[docs] def updateScrollBars(self): """ Update the scroll bars to accurately show where we are relative to the full extent """ fullextent = self.layers.getFullExtent() setbars = False self.suppressscrollevent = True if fullextent is not None: (fullleft, fulltop, fullright, fullbottom) = fullextent layer = self.layers.getTopLayer() if layer is not None: (left, top, right, bottom) = layer.coordmgr.getWorldExtent() (wldX, wldY) = layer.coordmgr.getWorldCenter() verticalBar = self.verticalScrollBar() horizontalBar = self.horizontalScrollBar() # always set range to 0 - 1000 and calculate # everything as fraction of that verticalBar.setRange(0, 1000) horizontalBar.setRange(0, 1000) # to pagestep which is also the slider size fullxsize = float(fullright - fullleft) if fullxsize == 0: fullxsize = 100 hpagestep = (float(right - left) / fullxsize) * 1000 horizontalBar.setPageStep(int(hpagestep)) fullysize = float(fulltop - fullbottom) if fullysize == 0: fullysize = 100 vpagestep = (float(top - bottom) / fullysize) * 1000 verticalBar.setPageStep(int(vpagestep)) # position of the slider relative to the center of the image hpos = (float(wldX - fullleft) / fullxsize) * 1000 horizontalBar.setSliderPosition(int(hpos)) vpos = (float(fulltop - wldY) / fullysize) * 1000 verticalBar.setSliderPosition(int(vpos)) setbars = True if not setbars: # something went wrong - disable self.horizontalScrollBar().setRange(0, 0) self.verticalScrollBar().setRange(0, 0) self.suppressscrollevent = False
[docs] def addRasterLayer(self, gdalDataset, stretch, lut=None, ignoreProjectionMismatch=False, quiet=False): """ Add the given dataset to the stack of images being displayed as a raster layer """ size = self.viewport().size() self.layers.addRasterLayer(gdalDataset, size.width(), size.height(), stretch, lut, ignoreProjectionMismatch, quiet) # get rid off tool points self.toolPoints = None self.toolPointsFinished = True self.viewport().update() self.updateScrollBars() self.layerAdded.emit(self)
[docs] def addVectorLayer(self, ogrDataSource, ogrLayer, color=None, resultSet=False, origSQL=None, label=None, quiet=False): """ Add the vector given by the ogrDataSource and its dependent ogrLayer to the stack of images. """ size = self.viewport().size() if color is None: color = viewerlayers.DEFAULT_VECTOR_COLOR self.layers.addVectorLayer(ogrDataSource, ogrLayer, size.width(), size.height(), color, resultSet, origSQL, label, quiet) self.viewport().update() self.updateScrollBars() self.layerAdded.emit(self)
[docs] def addVectorFeatureLayer(self, ogrDataSource, ogrLayer, ogrFeature, color=None, quiet=None): """ Just a single feature vector """ size = self.viewport().size() if color is None: color = viewerlayers.DEFAULT_VECTOR_COLOR self.layers.addVectorFeatureLayer(ogrDataSource, ogrLayer, ogrFeature, size.width(), size.height(), color, quiet) self.viewport().update() self.updateScrollBars() self.layerAdded.emit(self)
[docs] def addLayersFromJSONFile(self, fileobj, nlayers): """ Get the layer manager to read all the layer descriptions from fileobj and load these layers into this widget. """ size = self.viewport().size() self.layers.fromFile(fileobj, nlayers, size.width(), size.height()) self.viewport().update() self.updateScrollBars() self.layerAdded.emit(self)
[docs] def removeLayer(self): """ Removes the top later """ self.layers.removeTopLayer() # get rid of tool points self.toolPoints = None self.toolPointsFinished = True self.viewport().update() self.updateScrollBars()
[docs] def removeLayers(self, layers): """ Remove the given list of layers """ for layer in layers: self.layers.removeLayer(layer) # get rid of tool points self.toolPoints = None self.toolPointsFinished = True self.viewport().update() self.updateScrollBars()
# query point functions
[docs] def setQueryPoint(self, senderid, easting, northing, color, size=None, cursor=None): """ Sets/Updates query point keyed on the id() of the sender """ self.layers.queryPointLayer.setQueryPoint(senderid, easting, northing, color, size, cursor) self.layers.queryPointLayer.getImage() self.viewport().update()
[docs] def removeQueryPoint(self, senderid): """ Removes a query point. keyed on the id() of the sender """ self.layers.queryPointLayer.removeQueryPoint(senderid) self.layers.queryPointLayer.getImage() self.viewport().update()
[docs] def highlightValues(self, color, selectionArray=None): """ Applies a QColor to the LUT where selectionArray == True to the top layer and redraws. Pass None to reset """ layer = self.layers.getTopRasterLayer() if layer is not None: if len(layer.stretch.bands) != 1: msg = 'can only highlight values on single band images' raise viewererrors.InvalidDataset(msg) layer.highlightRows(color, selectionArray) # force repaint self.viewport().update()
[docs] def setColorTableLookup(self, lookupArray=None, colName=None, surrogateLUT=None, surrogateName=None): """ Uses the supplied lookupArray to look up image data before indexing into color table in the top layer and redraws. Pass None to reset. """ layer = self.layers.getTopRasterLayer() if layer is not None: if len(layer.stretch.bands) != 1: msg = 'can only highlight values on single band images' raise viewererrors.InvalidDataset(msg) layer.setColorTableLookup(lookupArray, colName, surrogateLUT, surrogateName) # force repaint self.viewport().update()
[docs] def zoomNativeResolution(self): """ Sets the zoom to native resolution wherever the current viewport is centered """ self.layers.zoomNativeResolution() # force repaint self.viewport().update() self.updateScrollBars() # geolink self.emitGeolinkMoved()
[docs] def zoomFullExtent(self): """ Resets the zoom to full extent - should be the same as when file was opened. """ self.layers.zoomFullExtent() # force repaint self.viewport().update() self.updateScrollBars() # geolink self.emitGeolinkMoved()
[docs] def setActiveTool(self, tool, senderid): """ Set active tool (one of VIEWER_TOOL_*). pass VIEWER_TOOL_NONE to disable pass the id() of the calling object. This is passed around in the activeToolChanged signal so GUI elements can recognise who asked for the change """ # if the tool was line or polygon # now is the time to remove the outline from the widget if (self.activeTool == VIEWER_TOOL_POLYGON or self.activeTool == VIEWER_TOOL_POLYLINE): self.toolPoints = None self.toolPointsFinished = True # force repaint self.viewport().update() self.activeTool = tool if tool == VIEWER_TOOL_ZOOMIN: if self.zoomInCursor is None: # create if needed. self.zoomInCursor = QCursor(QPixmap(["16 16 3 1", ". c None", "a c #000000", "# c #ffffff", ".....#####......", "...##aaaaa##....", "..#.a.....a.#...", ".#.a...a...a.#..", ".#a....a....a#..", "#a.....a.....a#.", "#a.....a.....a#.", "#a.aaaa#aaaa.a#.", "#a.....a.....a#.", "#a.....a.....a#.", ".#a....a....a#..", ".#.a...a...aaa#.", "..#.a.....a#aaa#", "...##aaaaa###aa#", ".....#####...###", "..............#."])) self.viewport().setCursor(self.zoomInCursor) elif tool == VIEWER_TOOL_ZOOMOUT: if self.zoomOutCursor is None: # create if needed self.zoomOutCursor = QCursor(QPixmap(["16 16 4 1", "b c None", ". c None", "a c #000000", "# c #ffffff", ".....#####......", "...##aaaaa##....", "..#.a.....a.#...", ".#.a.......a.#..", ".#a.........a#..", "#a...........a#.", "#a...........a#.", "#a.aaaa#aaaa.a#.", "#a...........a#.", "#a...........a#.", ".#a.........a#..", ".#.a.......aaa#.", "..#.a.....a#aaa#", "...##aaaaa###aa#", ".....#####...###", "..............#."])) self.viewport().setCursor(self.zoomOutCursor) elif tool == VIEWER_TOOL_PAN: if self.panCursor is None: # both these used for pan operations self.panCursor = QCursor(Qt.OpenHandCursor) self.panGrabCursor = QCursor(Qt.ClosedHandCursor) self.viewport().setCursor(self.panCursor) elif tool == VIEWER_TOOL_QUERY: if self.queryCursor is None: self.queryCursor = QCursor(Qt.CrossCursor) self.viewport().setCursor(self.queryCursor) elif tool == VIEWER_TOOL_VECTORQUERY: if self.vectorQueryCursor is None: self.vectorQueryCursor = QCursor(QPixmap(["16 16 3 1", "# c None", "a c #000000", ". c #ffffff", "######aaaa######", "######a..a######", "######a..a######", "######a..a######", "######a..a######", "######aaaa######", "aaaaa######aaaaa", "a...a######a...a", "a...a######a...a", "aaaaa######aaaaa", "######aaaa######", "######a..a###a.a", "######a..a###aaa", "######a..a###a.a", "######a..a###a.a", "######aaaa###aaa"])) self.viewport().setCursor(self.vectorQueryCursor) elif tool == VIEWER_TOOL_POLYGON or tool == VIEWER_TOOL_POLYLINE: if self.polygonCursor is None: self.polygonCursor = QCursor(QPixmap(["16 16 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ "])) self.viewport().setCursor(self.polygonCursor) msg = ('Left click adds a point, middle to remove last,' + ' right click to end') self.showStatusMessage.emit(msg) elif tool == VIEWER_TOOL_NONE: # change back self.viewport().setCursor(Qt.ArrowCursor) obj = ActiveToolChangedInfo(self.activeTool, senderid) self.activeToolChanged.emit(obj)
[docs] def setNewStretch(self, newstretch, layer, local=False): """ Change the stretch being applied to the current data """ layer.setNewStretch(newstretch, local) self.viewport().update()
[docs] def timeseriesBackward(self): """ Assume images are a stacked timeseries oldest to newest. Turn on the previous one to the current topmost displayed """ self.layers.timeseriesBackward() self.viewport().update()
[docs] def timeseriesForward(self): """ Assume images are a stacked timeseries oldest to newest. Turn off the current topmost displayed """ self.layers.timeseriesForward() self.viewport().update()
[docs] def setMouseScrollWheelAction(self, scrollZoom): "Set the action for a mouse wheen event (scroll/zoom)" self.mouseWheelZoom = scrollZoom
[docs] def setBackgroundColor(self, color): "Sets the background color for the widget" widget = self.viewport() palette = widget.palette() palette.setColor(widget.backgroundRole(), color) widget.setPalette(palette)
[docs] def setGeolinkFollowExtentAction(self, followExtent): "Set whether we are following geolink extent of just center" self.geolinkFollowExtent = followExtent
[docs] def setQueryOnlyDisplayed(self, queryOnlyDisplayed): """ set whether we are only querying displayed layers (True) or all (False) """ self.queryOnlyDisplayed = queryOnlyDisplayed
[docs] def flicker(self): """ Call to change the flicker state (ie draw the top raster image or not). """ state = False layer = self.layers.getTopLayer() if layer is not None: state = not layer.displayed self.layers.setDisplayedState(layer, state) self.viewport().update() return state
[docs] def scrollContentsBy(self, dx, dy): """ Handle the user moving the scroll bars """ if not self.suppressscrollevent: layer = self.layers.getTopLayer() if layer is not None: (left, top, right, bottom) = layer.coordmgr.getWorldExtent() hpagestep = float(self.horizontalScrollBar().pageStep()) xamount = -(float(dx) / hpagestep) * (right - left) vpagestep = float(self.verticalScrollBar().pageStep()) yamount = (float(dy) / vpagestep) * (top - bottom) wldX, wldY = layer.coordmgr.getWorldCenter() layer.coordmgr.setWorldCenter(wldX + xamount, wldY + yamount) # not sure why we need this but get black strips # around otherwise layer.coordmgr.recalcBottomRight() self.layers.makeLayersConsistent(layer) self.layers.updateImages() self.viewport().update() self.updateScrollBars() # geolink self.emitGeolinkMoved()
[docs] def wheelEvent(self, event): """ User has used mouse wheel to zoom in/out or pan depending on defined preference """ layer = self.layers.getTopRasterLayer() if layer is not None: delta = event.angleDelta().y() # Shift scrolling will move you forward and backwards through the time series if QApplication.keyboardModifiers() == Qt.ShiftModifier: if delta > 0: self.timeseriesBackward() elif delta < 0: self.timeseriesForward() elif self.mouseWheelZoom: (wldX, wldY) = layer.coordmgr.getWorldCenter() impixperwinpix = layer.coordmgr.imgPixPerWinPix if delta > 0: impixperwinpix *= 1.0 - VIEWER_ZOOM_WHEEL_FRACTION elif delta < 0: impixperwinpix *= 1.0 + VIEWER_ZOOM_WHEEL_FRACTION layer.coordmgr.setZoomFactor(impixperwinpix) layer.coordmgr.setWorldCenter(wldX, wldY) # not sure why we need this but get black strips # around otherwise layer.coordmgr.recalcBottomRight() self.layers.makeLayersConsistent(layer) self.layers.updateImages() self.updateScrollBars() self.viewport().update() else: dx = event.angleDelta().x() dy = delta self.scrollContentsBy(dx, dy) # geolink self.emitGeolinkMoved()
[docs] def resizeEvent(self, event): """ Window has been resized - get new data """ size = event.size() self.layers.setDisplaySize(size.width(), size.height()) self.updateScrollBars()
[docs] def paintEvent(self, event): """ Viewport needs to be redrawn. Assume that each layer's image is current (as created by getImage()) we can just draw it with QPainter """ paint = QPainter(self.viewport()) for layer in self.layers.layers: if layer.displayed: paint.drawImage(self.paintPoint, layer.image) # draw any query points on top of image paint.drawImage(self.paintPoint, self.layers.queryPointLayer.image) # now any tool points if self.toolPoints is not None: path = QPainterPath() firstpt = self.toolPoints[0] path.moveTo(firstpt.x(), firstpt.y()) for pt in self.toolPoints[1:]: path.lineTo(pt.x(), pt.y()) paint.setPen(self.toolPen) paint.drawPath(path) paint.end()
[docs] def mousePressEvent(self, event): """ Mouse has been clicked down if we are in zoom/pan mode we need to start doing stuff here """ QAbstractScrollArea.mousePressEvent(self, event) pos = event.pos() if (self.activeTool == VIEWER_TOOL_ZOOMIN or self.activeTool == VIEWER_TOOL_ZOOMOUT): if self.rubberBand is None: self.rubberBand = QRubberBand(QRubberBand.Rectangle, self) self.rubberBand.setGeometry(QRect(pos, QSize())) self.rubberBand.show() self.rubberBand.origin = pos elif self.activeTool == VIEWER_TOOL_PAN: # remember pos self.panOrigin = pos # change cursor self.viewport().setCursor(self.panGrabCursor) elif self.activeTool == VIEWER_TOOL_QUERY: modifiers = event.modifiers() (dspX, dspY) = (pos.x(), pos.y()) self.newQueryPoint(dspX=dspX, dspY=dspY, modifiers=modifiers) elif self.activeTool == VIEWER_TOOL_VECTORQUERY: modifiers = event.modifiers() (dspX, dspY) = (pos.x(), pos.y()) self.newVectorQueryPoint(dspX, dspY, modifiers=modifiers) elif self.activeTool == VIEWER_TOOL_POLYGON: button = event.button() if button == Qt.LeftButton: # adding points if self.toolPoints is None or self.toolPointsFinished: # first point - starts and ends at same pos self.toolPoints = [pos, pos] self.toolPointsFinished = False else: # last point same as first - insert before last self.toolPoints.insert(-1, pos) elif button == Qt.MiddleButton and self.toolPoints is not None: # delete last point if len(self.toolPoints) > 2: del self.toolPoints[-2] elif button == Qt.RightButton and self.toolPoints is not None: # finished # create object for signal layer = self.layers.getTopRasterLayer() modifiers = event.modifiers() obj = PolygonToolInfo(self.toolPoints, layer, modifiers) self.polygonCollected.emit(obj) self.toolPointsFinished = True # done, but still display # redraw so paint() gets called self.viewport().update() elif self.activeTool == VIEWER_TOOL_POLYLINE: button = event.button() if button == Qt.LeftButton: # adding points if self.toolPoints is None or self.toolPointsFinished: # first point self.toolPoints = [pos] self.toolPointsFinished = False else: # add to list self.toolPoints.append(pos) elif button == Qt.MiddleButton and self.toolPoints is not None: # delete last point if len(self.toolPoints) > 1: self.toolPoints.pop() elif button == Qt.RightButton and self.toolPoints is not None: # finished # create object for signal if self.queryOnlyDisplayed: layer = self.layers.getTopDisplayedRasterLayer() else: layer = self.layers.getTopRasterLayer() modifiers = event.modifiers() obj = PolylineToolInfo(self.toolPoints, layer, modifiers) self.polylineCollected.emit(obj) self.toolPointsFinished = True # done, but still display # redraw so paint() gets called self.viewport().update()
[docs] def mouseReleaseEvent(self, event): """ Mouse has been released, if we are in zoom/pan mode we do stuff here. """ QAbstractScrollArea.mouseReleaseEvent(self, event) if self.rubberBand is not None and self.rubberBand.isVisible(): # get the information about the rect they have drawn # note this is on self, rather than viewport() selection = self.rubberBand.geometry() geom = self.viewport().geometry() selectionsize = float(selection.width() * selection.height()) geomsize = float(geom.width() * geom.height()) self.rubberBand.hide() layer = self.layers.getTopRasterLayer() if layer is not None: if selectionsize < MIN_SELECTION_SIZE_PX: # this is practically a '0' size selection on a 4K screen and # an improbably small selection on an HD screen so assume user # has just clicked the image and set fraction to 0.5 fraction = 0.5 else: fraction = numpy.sqrt(selectionsize / geomsize) if self.activeTool == VIEWER_TOOL_ZOOMIN: if selectionsize < MIN_SELECTION_SIZE_PX: # user 'just clicked' (see if statement above). # NOTE: this fixes issues with 0 or negative dimensions # inside else: below that used ot hard-crash tuiview wldX, wldY = layer.coordmgr.display2world( selection.left(), selection.top()) layer.coordmgr.setZoomFactor( layer.coordmgr.imgPixPerWinPix * fraction) layer.coordmgr.setWorldCenter(wldX, wldY) # not sure why we need this but get black strips # around otherwise layer.coordmgr.recalcBottomRight() else: # I don't think anything needs to be added here dspTop = selection.top() dspLeft = selection.left() dspBottom = selection.bottom() dspRight = selection.right() (rastLeft, rastTop) = layer.coordmgr.display2pixel( dspLeft, dspTop) (rastRight, rastBottom) = layer.coordmgr.display2pixel( dspRight, dspBottom) # print layer.coordmgr layer.coordmgr.setTopLeftPixel(rastLeft, rastTop) layer.coordmgr.calcZoomFactor(rastRight, rastBottom) # not sure why we need this but get black strips # around otherwise layer.coordmgr.recalcBottomRight() # print layer.coordmgr elif self.activeTool == VIEWER_TOOL_ZOOMOUT: # the smaller the area the larger the zoom center = selection.center() wldX, wldY = layer.coordmgr.display2world(center.x(), center.y()) layer.coordmgr.setZoomFactor( layer.coordmgr.imgPixPerWinPix / fraction) layer.coordmgr.setWorldCenter(wldX, wldY) # not sure why we need this but get black strips # around otherwise layer.coordmgr.recalcBottomRight() # redraw self.layers.makeLayersConsistent(layer) self.layers.updateImages() self.viewport().update() self.updateScrollBars() # geolink self.emitGeolinkMoved() elif self.activeTool == VIEWER_TOOL_PAN: # change cursor back self.viewport().setCursor(self.panCursor) layer = self.layers.getTopRasterLayer() if layer is not None: # stop panning and move viewport dspXmove = -self.paintPoint.x() dspYmove = -self.paintPoint.y() (pixNewX, pixNewY) = layer.coordmgr.display2pixel(dspXmove, dspYmove) # print 'panning' # print layer.coordmgr layer.coordmgr.setTopLeftPixel(pixNewX, pixNewY) layer.coordmgr.recalcBottomRight() # print layer.coordmgr # reset self.paintPoint.setX(0) self.paintPoint.setY(0) # redraw self.layers.makeLayersConsistent(layer) self.layers.updateImages() self.viewport().update() self.updateScrollBars() # geolink self.emitGeolinkMoved()
[docs] def mouseMoveEvent(self, event): """ Mouse has been moved while dragging. If in zoom/pan mode we need to do something here. """ QAbstractScrollArea.mouseMoveEvent(self, event) if self.rubberBand is not None and self.rubberBand.isVisible(): # must be doing zoom in/out. extend rect rect = QRect(self.rubberBand.origin, event.pos()).normalized() self.rubberBand.setGeometry(rect) elif self.activeTool == VIEWER_TOOL_PAN: # panning. Work out the offset from where we # starting panning and draw the current image # at an offset pos = event.pos() xamount = pos.x() - self.panOrigin.x() yamount = pos.y() - self.panOrigin.y() self.paintPoint.setX(xamount) self.paintPoint.setY(yamount) # force repaint - self.paintPoint used by paintEvent() self.viewport().update() self.updateScrollBars()
# query point routines
[docs] def newQueryPoint(self, easting=None, northing=None, dspY=None, dspX=None, column=None, row=None, lat=None, long=None, modifiers=None): """ This viewer has recorded a new query point. Or user has entered new coords in querywindow. Calls updateQueryPoint and emits the geolinkQueryPoint signal pass either [easting and northing], [dspX,dspY], [column, row] or [long, lat] """ if self.queryOnlyDisplayed: layer = self.layers.getTopDisplayedRasterLayer() else: layer = self.layers.getTopRasterLayer() if layer is None: return if ((easting is None or northing is None) and (dspX is None or dspY is None) and (column is None or row is None) and (lat is None or long is None)): msg = ("must provide one of [easting,northing] or [dspX,dspY] " + "or [column, row] or [long, lat]") raise ValueError(msg) if dspX is not None and dspY is not None: (column, row) = layer.coordmgr.display2pixel(dspX, dspY) (easting, northing) = layer.coordmgr.pixel2world(column, row) (long, lat) = layer.toLatLong(easting, northing) elif easting is not None and northing is not None: (column, row) = layer.coordmgr.world2pixel(easting, northing) (long, lat) = layer.toLatLong(easting, northing) elif column is not None and row is not None: (easting, northing) = layer.coordmgr.pixel2world(column, row) (long, lat) = layer.toLatLong(easting, northing) elif long is not None and lat is not None: (easting, northing) = layer.toEastingNorthing(long, lat) (column, row) = layer.coordmgr.world2pixel(easting, northing) # update the point self.updateQueryPoint(easting, northing, column, row, long, lat, modifiers) # emit the geolinked query point signal obj = GeolinkInfo(id(self), easting, northing) self.geolinkQueryPoint.emit(obj)
[docs] def newVectorQueryPoint(self, dspX, dspY, modifiers=None): """ New vector query point. Does the spatial query and emits the vectorLocationSelected signal with the results """ if self.queryOnlyDisplayed: layer = self.layers.getTopDisplayedVectorLayer() else: layer = self.layers.getTopVectorLayer() if layer is None: return (easting, northing) = layer.coordmgr.display2world(dspX, dspY) tolerance = layer.coordmgr.metersperpix * 3 # maybe should be a pref? # show hourglass while query running oldCursor = self.cursor() self.setCursor(Qt.WaitCursor) results = layer.getAttributesAtPoint(easting, northing, tolerance) self.setCursor(oldCursor) self.vectorLocationSelected.emit(results, layer)
[docs] def updateQueryPoint(self, easting, northing, column, row, long, lat, modifiers): """ Map has been clicked, get the value and emit a locationSelected signal. Called by newQueryPoint or when a geolinkQueryPoint signal has been received. """ # read the data out of the dataset if self.queryOnlyDisplayed: layer = self.layers.getTopDisplayedRasterLayer() else: layer = self.layers.getTopRasterLayer() if (layer is not None and column >= 0 and column < layer.gdalDataset.RasterXSize and row >= 0 and row < layer.gdalDataset.RasterYSize): data = layer.gdalDataset.ReadAsArray(int(column), int(row), 1, 1) if data is not None: # we just want the single 'drill down' of data as a 1d array data = data[..., 0, 0] # if single band GDAL gives us a single value - # convert back to array # to make life easier if data.size == 1: data = numpy.array([data]) qi = QueryInfo(easting, northing, column, row, long, lat, data, layer, modifiers) # emit the signal - handled by the QueryDockWidget self.locationSelected.emit(qi)
[docs] def doGeolinkQueryPoint(self, easting, northing): """ Call this when the widget query point has been moved in another viewer and should be updated in this one if the query tool is active. """ if self.activeTool == VIEWER_TOOL_QUERY: if self.queryOnlyDisplayed: layer = self.layers.getTopDisplayedRasterLayer() else: layer = self.layers.getTopRasterLayer() if layer is not None: (col, row) = layer.coordmgr.world2pixel(easting, northing) (long, lat) = layer.toLatLong(easting, northing) self.updateQueryPoint(easting, northing, col, row, long, lat, None)
# geolinking routines
[docs] def doGeolinkMove(self, easting, northing, metresperwinpix): """ Call this when widget needs to be moved because of geolinking event. """ layer = self.layers.getTopRasterLayer() if layer is not None: if self.geolinkFollowExtent and metresperwinpix != 0: imgpixperwinpix = metresperwinpix / layer.coordmgr.geotransform[1] layer.coordmgr.setZoomFactor(imgpixperwinpix) layer.coordmgr.setWorldCenter(easting, northing) self.layers.makeLayersConsistent(layer) self.layers.updateImages() self.updateScrollBars() self.viewport().update()
[docs] def getGeolinkInfo(self): """ Called by emitGeolinkMoved and anything else that needs the current GeolinkInfo """ info = None # get the coords of the current centre layer = self.layers.getTopRasterLayer() if layer is not None: easting, northing = layer.coordmgr.getWorldCenter() metresperwinpix = (layer.coordmgr.imgPixPerWinPix * layer.coordmgr.geotransform[1]) info = GeolinkInfo(id(self), easting, northing, metresperwinpix) return info
[docs] def emitGeolinkMoved(self): """ Call this on each zoom/pan to emit the appropriate signal. """ info = self.getGeolinkInfo() if info is not None: # emit the signal self.geolinkMove.emit(info)