讓我們開發一個非常好用的捲簾工具
阿新 • • 發佈:2018-12-12
版權宣告:未經作者允許不得轉載,此外掛不得用於商業用途。
目錄
開發環境
- QGIS 3.2
- python(QGIS自帶)
外掛開發
關於QGIS中使用python開發外掛的方法,自行檢視官方文件(https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/plugins.html
__init__.py
from .map_swipe_plugin import MapSwipePlugin
def classFactory(iface):
return MapSwipePlugin(iface)
map_swipe_plugin.py
import os from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget, QHBoxLayout, QComboBox, QPushButton from qgis.core import QgsProject from qgis.gui import QgsMapToolPan from .map_swipe_tool import MapSwipeTool plugin_path = os.path.dirname(__file__) class MapSwipePlugin: def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() # 圖層變化訊號 QgsProject.instance().layerTreeRoot().layerOrderChanged.connect(self.combobox_add_items) def initGui(self): self.menu = self.title = "捲簾工具" self._create_widget() self.tool = MapSwipeTool(plugin_path, self.combobox, self.iface) self.tool.deactivated.connect(self.tool_deactivated) self.widget_action = self.iface.addToolBarWidget(self.widget) def unload(self): self.canvas.setMapTool(QgsMapToolPan(self.iface.mapCanvas())) self.iface.removeToolBarIcon(self.widget_action) del self.widget_action def run(self): if self.canvas.mapTool() != self.tool: self.prevTool = self.canvas.mapTool() self.canvas.setMapTool(self.tool) else: self.canvas.setMapTool(self.prevTool) if self.pushbutton.isChecked() and self.combobox.isHidden(): self.combobox.show() self.combobox_add_items() else: self.combobox.hide() def _create_widget(self): icon = QIcon(os.path.join(plugin_path, 'icon.png')) # 新建widget self.widget = QWidget(self.iface.mainWindow()) self.hlayout = QHBoxLayout(self.widget) self.hlayout.setContentsMargins(0, 0, 0, 0) self.pushbutton = QPushButton(icon, '', self.widget) self.pushbutton.setCheckable(True) self.pushbutton.setFlat(True) self.combobox = QComboBox(self.widget) self.hlayout.addWidget(self.pushbutton) self.hlayout.addWidget(self.combobox) self.combobox.hide() self.combobox_add_items() self.pushbutton.clicked.connect(self.run) def combobox_add_items(self): self.combobox.clear() layers = QgsProject.instance().layerTreeRoot().layerOrder() self.combobox.addItems([layer.name() for layer in layers]) def tool_deactivated(self): '''tool非啟用狀態''' self.pushbutton.setChecked(False) self.combobox.hide()
map_swipe_tool.py
import os from PyQt5.QtCore import Qt, QPoint from PyQt5.QtGui import QCursor, QPixmap from qgis.gui import QgsMapTool from qgis.core import QgsProject, QgsMapSettings, QgsMapRendererParallelJob from .swipe_map import SwipeMap class MapSwipeTool(QgsMapTool): def __init__(self, plugin_path, combobox, iface): super(MapSwipeTool, self).__init__(iface.mapCanvas()) self.combobox = combobox self.map_canvas = iface.mapCanvas() self.view = iface.layerTreeView() self.swipe = SwipeMap(self.map_canvas) self.hasSwipe = None self.start_point = QPoint() self.cursorSV = QCursor(QPixmap(os.path.join(plugin_path, 'images/split_v.png'))) self.cursorSH = QCursor(QPixmap(os.path.join(plugin_path, 'images/split_h.png'))) self.cursorUP = QCursor(QPixmap(os.path.join(plugin_path, 'images/up.png'))) self.cursorDOWN = QCursor(QPixmap(os.path.join(plugin_path, 'images/down.png'))) self.cursorLEFT = QCursor(QPixmap(os.path.join(plugin_path, 'images/left.png'))) self.cursorRIGHT = QCursor(QPixmap(os.path.join(plugin_path, 'images/right.png'))) def activate(self): self.map_canvas.setCursor(QCursor(Qt.CrossCursor)) self._connect() self.hasSwipe = False self.setLayersSwipe() def canvasPressEvent(self, e): self.hasSwipe = True direction = None w, h = self.map_canvas.width(), self.map_canvas.height() if 0.25 * w < e.x() < 0.75 * w and e.y() < 0.5 * h: direction = 0 # '⬇' self.swipe.isVertical = False if 0.25 * w < e.x() < 0.75 * w and e.y() > 0.5 * h: direction = 1 # '⬆' self.swipe.isVertical = False if e.x() < 0.25 * w: direction = 2 # '➡' self.swipe.isVertical = True if e.x() > 0.75 * w: direction = 3 # '⬅' self.swipe.isVertical = True self.swipe.set_direction(direction) self.map_canvas.setCursor(self.cursorSH if self.swipe.isVertical else self.cursorSV) self.swipe.set_img_extent(e.x(), e.y()) def canvasReleaseEvent(self, e): self.hasSwipe = False self.canvasMoveEvent(e) # 滑鼠釋放後,移除繪製的線 self.swipe.set_img_extent(-9999, -9999) def canvasMoveEvent(self, e): if self.hasSwipe: self.swipe.set_img_extent(e.x(), e.y()) else: # 設定當前cursor w, h = self.map_canvas.width(), self.map_canvas.height() if e.x() < 0.25 * w: self.canvas().setCursor(self.cursorRIGHT) if e.x() > 0.75 * w: self.canvas().setCursor(self.cursorLEFT) if 0.25 * w < e.x() < 0.75 * w and e.y() < 0.5 * h: self.canvas().setCursor(self.cursorDOWN) if 0.25 * w < e.x() < 0.75 * w and e.y() > 0.5 * h: self.canvas().setCursor(self.cursorUP) def _connect(self, isConnect=True): signal_slot = ( {'signal': self.map_canvas.mapCanvasRefreshed, 'slot': self.setMap}, {'signal': self.combobox.currentIndexChanged, 'slot': self.setLayersSwipe}, {'signal': QgsProject.instance().removeAll, 'slot': self.disable} ) if isConnect: for item in signal_slot: item['signal'].connect(item['slot']) else: for item in signal_slot: item['signal'].disconnect(item['slot']) def setLayersSwipe(self, ): current_layer = QgsProject.instance().mapLayersByName(self.combobox.currentText()) if len(current_layer) == 0: return layers = QgsProject.instance().layerTreeRoot().layerOrder() layer_list = [] for layer in layers: if layer.id() == current_layer[0].id(): continue layer_list.append(layer) self.swipe.clear() self.swipe.setLayersId(layer_list) self.setMap() def disable(self): self.swipe.clear() self.hasSwipe = False def deactivate(self): self.deactivated.emit() self.swipe.clear() self._connect(False) def setMap(self): def finished(): self.swipe.setContent(job.renderedImage(), self.map_canvas.extent()) if len(self.swipe.layers) == 0: return settings = QgsMapSettings(self.map_canvas.mapSettings()) settings.setLayers(self.swipe.layers) job = QgsMapRendererParallelJob(settings) job.start() job.finished.connect(finished) job.waitForFinished()
MapSwipeTool繼承了QgsMapTool類,重新實現了activate、deactivate、canvasPressEvent、canvasReleaseEvent、canvasMoveEvent方法,還用到了QgsMapRendererParallelJob類,QgsMapRendererParallelJob是QGIS提供的並行繪圖類,不做詳解,下面對重新實現的方法詳細說明:
active
工具啟用後執行此函式
deactivate
工具非啟用狀態執行此函式
canvasPressEvent
def canvasPressEvent(self, e):
self.hasSwipe = True
direction = None
w, h = self.map_canvas.width(), self.map_canvas.height()
if 0.25 * w < e.x() < 0.75 * w and e.y() < 0.5 * h:
direction = 0 # '⬇'
self.swipe.isVertical = False
if 0.25 * w < e.x() < 0.75 * w and e.y() > 0.5 * h:
direction = 1 # '⬆'
self.swipe.isVertical = False
if e.x() < 0.25 * w:
direction = 2 # '➡'
self.swipe.isVertical = True
if e.x() > 0.75 * w:
direction = 3 # '⬅'
self.swipe.isVertical = True
self.swipe.set_direction(direction)
self.map_canvas.setCursor(self.cursorSH if self.swipe.isVertical else self.cursorSV)
self.swipe.set_img_extent(e.x(), e.y())
畫布的滑鼠按壓事件,將畫布分為四個部分,獲得捲簾的方向,然後設定滑鼠游標,具體實現方式自行腦補,原理如下圖:
canvasReleaseEvent
def canvasReleaseEvent(self, e):
self.hasSwipe = False
self.canvasMoveEvent(e)
# 滑鼠釋放後,移除繪製的線
self.swipe.set_img_extent(-9999, -9999)
畫布的滑鼠釋放事件,滑鼠釋放後要把繪製的線移出畫布
self.swipe.set_img_extent(-9999, -9999)
canvasMoveEvent
def canvasMoveEvent(self, e):
if self.hasSwipe:
self.swipe.set_img_extent(e.x(), e.y())
else:
# 設定當前cursor
w, h = self.map_canvas.width(), self.map_canvas.height()
if e.x() < 0.25 * w:
self.canvas().setCursor(self.cursorRIGHT)
if e.x() > 0.75 * w:
self.canvas().setCursor(self.cursorLEFT)
if 0.25 * w < e.x() < 0.75 * w and e.y() < 0.5 * h:
self.canvas().setCursor(self.cursorDOWN)
if 0.25 * w < e.x() < 0.75 * w and e.y() > 0.5 * h:
self.canvas().setCursor(self.cursorUP)
畫布的滑鼠移動事件,當使用捲簾時,獲得滑鼠的位置(e.x(),e.y()),當不使用捲簾時,根據滑鼠的位置設定滑鼠的游標形狀,原理與canvasPressEvent相同
swipe_map.py
from PyQt5.QtCore import QRectF, QPointF, Qt
from PyQt5.QtGui import QPen, QColor
from qgis.gui import QgsMapCanvasItem
class SwipeMap(QgsMapCanvasItem):
def __init__(self, canvas):
super(SwipeMap, self).__init__(canvas)
self.length = 0
self.isVertical = True
self.layers = []
self.is_paint = False
def setContent(self, image, rect):
self.copyimage = image
self.setRect(rect)
def clear(self):
del self.layers[:]
self.is_paint = False
def setLayersId(self, layers):
del self.layers[:]
for item in layers:
self.layers.append(item)
def set_direction(self, direction):
# 0:'⬇', 1:'⬆', 2:'➡', 3:'⬅'
if direction == 0:
self.direction = 0
elif direction == 1:
self.direction = 1
elif direction == 2:
self.direction = 2
else:
self.direction = 3
self.startx, self.starty, self.endx, self.endy = 0, 0, self.boundingRect().width(), self.boundingRect().height()
def set_img_extent(self, x, y):
self.x = x
self.y = y
if self.direction == 0: # 0:'⬇'
self.endy = y
elif self.direction == 1: # 1:'⬆'
self.starty = y
elif self.direction == 2: # 2:'➡'
self.endx = x
else: # 3:'⬅'
self.startx = x
self.is_paint = True
self.update()
def paint(self, painter, *args):
if len(self.layers) == 0 or self.is_paint == False:
return
w = self.boundingRect().width()
h = self.boundingRect().height()
pen = QPen(Qt.DashDotDotLine)
pen.setColor(QColor(18, 150, 219))
pen.setWidth(4)
if self.isVertical:
painter.setPen(pen)
painter.drawLine(QPointF(self.x, 0), QPointF(self.x, h))
else:
painter.setPen(pen)
painter.drawLine(QPointF(0, self.y), QPointF(w, self.y))
image = self.copyimage.copy(self.startx, self.starty, self.endx, self.endy)
painter.drawImage(QRectF(self.startx, self.starty, self.endx, self.endy), image)
此模組為繪製地圖模組,首先獲得繪製地圖的範圍
self.startx, self.starty, self.endx, self.endy = 0, 0, self.boundingRect().width(), self.boundingRect().height()
然後重寫繪製函式paint
def paint(self, painter, *args):
if len(self.layers) == 0 or self.is_paint == False:
return
w = self.boundingRect().width()
h = self.boundingRect().height()
pen = QPen(Qt.DashDotDotLine)
pen.setColor(QColor(18, 150, 219))
pen.setWidth(4)
if self.isVertical:
painter.setPen(pen)
painter.drawLine(QPointF(self.x, 0), QPointF(self.x, h))
else:
painter.setPen(pen)
painter.drawLine(QPointF(0, self.y), QPointF(w, self.y))
image = self.copyimage.copy(self.startx, self.starty, self.endx, self.endy)
painter.drawImage(QRectF(self.startx, self.starty, self.endx, self.endy), image)