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.

from __future__ import division # ensure we are using Python 3 semantics
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

[docs]class QueryInfo(object): """ Container class for the information passed in the locationSelected signal. """ def __init__(self, easting, northing, column, row, data, layer, modifiers): self.easting = easting self.northing = northing self.column = column self.row = row 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') geolinkQueryPoint = pyqtSignal(GeolinkInfo, name='geolinkQueryPoint') # can't use ViewerWidget - use base class instead layerAdded = pyqtSignal(QAbstractScrollArea, name='layerAdded') showStatusMessage = pyqtSignal('QString', name='showStatusMessage') activeToolChanged = pyqtSignal(ActiveToolChangedInfo, name='activeToolChanged') polygonCollected = pyqtSignal(PolygonToolInfo, name='polygonCollected') polylineCollected = pyqtSignal(PolylineToolInfo, name='polylineCollected') vectorLocationSelected = pyqtSignal(list, viewerlayers.ViewerVectorLayer, name='vectorLocationSelected') locationSelected = pyqtSignal(QueryInfo, name='locationSelected') 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, 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, 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 off 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 == 0: fraction = 0.5 # they just clicked else: fraction = numpy.sqrt(selectionsize / geomsize) if self.activeTool == VIEWER_TOOL_ZOOMIN: if selectionsize == 0: 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 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() # 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, 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] or [dspX,dspY] or [column, row] """ 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)): msg = ("must provide one of [easting,northing] or [dspX,dspY] " + "or [column, row]") 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) elif easting is not None and northing is not None: (column, row) = layer.coordmgr.world2pixel(easting, northing) elif column is not None and row is not None: (easting, northing) = layer.coordmgr.pixel2world(column, row) # update the point self.updateQueryPoint(easting, northing, column, row, 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, 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, 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) self.updateQueryPoint(easting, northing, col, row, 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)