class InstantPrintTool(QgsMapTool, InstantPrintDialog): def __init__(self, iface, populateCompositionFz=None): QgsMapTool.__init__(self, iface.mapCanvas()) self.iface = iface projectInstance = QgsProject.instance() self.projectLayoutManager = projectInstance.layoutManager() self.rubberband = None self.oldrubberband = None self.pressPos = None self.printer = QPrinter() self.mapitem = None self.populateCompositionFz = populateCompositionFz self.dialog = InstantPrintDialog(self.iface.mainWindow()) self.dialogui = Ui_InstantPrintDialog() self.dialogui.setupUi(self.dialog) self.dialogui.addScale.setIcon(QIcon(":/images/themes/default/mActionAdd.svg")) self.dialogui.deleteScale.setIcon(QIcon(":/images/themes/default/symbologyRemove.svg")) self.dialog.hidden.connect(self.__onDialogHidden) self.exportButton = self.dialogui.buttonBox.addButton(self.tr("Export"), QDialogButtonBox.ActionRole) self.printButton = self.dialogui.buttonBox.addButton(self.tr("Print"), QDialogButtonBox.ActionRole) self.helpButton = self.dialogui.buttonBox.addButton(self.tr("Help"), QDialogButtonBox.HelpRole) self.dialogui.comboBox_fileformat.addItem("PDF", self.tr("PDF Document (*.pdf);;")) self.dialogui.comboBox_fileformat.addItem("JPG", self.tr("JPG Image (*.jpg);;")) self.dialogui.comboBox_fileformat.addItem("BMP", self.tr("BMP Image (*.bmp);;")) self.dialogui.comboBox_fileformat.addItem("PNG", self.tr("PNG Image (*.png);;")) self.iface.layoutDesignerOpened.connect(lambda view: self.__reloadLayouts()) self.iface.layoutDesignerWillBeClosed.connect(self.__reloadLayouts) self.dialogui.comboBox_layouts.currentIndexChanged.connect(self.__selectLayout) self.dialogui.comboBox_scale.lineEdit().textChanged.connect(self.__changeScale) self.dialogui.comboBox_scale.scaleChanged.connect(self.__changeScale) self.exportButton.clicked.connect(self.__export) self.printButton.clicked.connect(self.__print) self.helpButton.clicked.connect(self.__help) self.dialogui.buttonBox.button(QDialogButtonBox.Close).clicked.connect(lambda: self.dialog.hide()) self.dialogui.addScale.clicked.connect(self.add_new_scale) self.dialogui.deleteScale.clicked.connect(self.remove_scale) self.deactivated.connect(self.__cleanup) self.setCursor(Qt.OpenHandCursor) settings = QSettings() if settings.value("instantprint/geometry") is not None: self.dialog.restoreGeometry(settings.value("instantprint/geometry")) if settings.value("instantprint/scales") is not None: for scale in settings.value("instantprint/scales").split(";"): if scale: self.retrieve_scales(scale) self.check_scales() def __onDialogHidden(self): self.setEnabled(False) self.iface.mapCanvas().unsetMapTool(self) QSettings().setValue("instantprint/geometry", self.dialog.saveGeometry()) list = [] for i in range(self.dialogui.comboBox_scale.count()): list.append(self.dialogui.comboBox_scale.itemText(i)) QSettings().setValue("instantprint/scales", ";".join(list)) def retrieve_scales(self, checkScale): if self.dialogui.comboBox_scale.findText(checkScale) == -1: self.dialogui.comboBox_scale.addItem(checkScale) def add_new_scale(self): new_layout = self.dialogui.comboBox_scale.currentText() if self.dialogui.comboBox_scale.findText(new_layout) == -1: self.dialogui.comboBox_scale.addItem(new_layout) self.check_scales() def remove_scale(self): layout_to_delete = self.dialogui.comboBox_scale.currentIndex() self.dialogui.comboBox_scale.removeItem(layout_to_delete) self.check_scales() def setEnabled(self, enabled): if enabled: self.dialog.setVisible(True) self.__reloadLayouts() self.iface.mapCanvas().setMapTool(self) else: self.dialog.setVisible(False) self.iface.mapCanvas().unsetMapTool(self) def __changeScale(self): if not self.mapitem: return newscale = self.dialogui.comboBox_scale.scale() if abs(newscale) < 1E-6: return extent = self.mapitem.extent() center = extent.center() newwidth = extent.width() / self.mapitem.scale() * newscale newheight = extent.height() / self.mapitem.scale() * newscale x1 = center.x() - 0.5 * newwidth y1 = center.y() - 0.5 * newheight x2 = center.x() + 0.5 * newwidth y2 = center.y() + 0.5 * newheight self.mapitem.setExtent(QgsRectangle(x1, y1, x2, y2)) self.__createRubberBand() self.check_scales() def __selectLayout(self): if not self.dialog.isVisible(): return activeIndex = self.dialogui.comboBox_layouts.currentIndex() if activeIndex < 0: return layoutView = self.dialogui.comboBox_layouts.itemData(activeIndex) maps = [] layout_name = self.dialogui.comboBox_layouts.currentText() layout = self.projectLayoutManager.layoutByName(layout_name) for item in layoutView.items(): if isinstance(item, QgsLayoutItemMap): maps.append(item) if len(maps) != 1: QMessageBox.warning(self.iface.mainWindow(), self.tr("Invalid layout"), self.tr("The layout must have exactly one map item.")) self.exportButton.setEnabled(False) self.iface.mapCanvas().scene().removeItem(self.rubberband) self.rubberband = None self.dialogui.comboBox_scale.setEnabled(False) return self.dialogui.comboBox_scale.setEnabled(True) self.exportButton.setEnabled(True) self.layoutView = layoutView self.mapitem = layout.referenceMap() self.dialogui.comboBox_scale.setScale(self.mapitem.scale()) self.__createRubberBand() def __createRubberBand(self): self.__cleanup() extent = self.mapitem.extent() center = self.iface.mapCanvas().extent().center() self.corner = QPointF(center.x() - 0.5 * extent.width(), center.y() - 0.5 * extent.height()) self.rect = QRectF(self.corner.x(), self.corner.y(), extent.width(), extent.height()) self.mapitem.setExtent(QgsRectangle(self.rect)) self.rubberband = QgsRubberBand(self.iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.rubberband.setToCanvasRectangle(self.__canvasRect(self.rect)) self.rubberband.setColor(QColor(127, 127, 255, 127)) self.pressPos = None def __cleanup(self): if self.rubberband: self.iface.mapCanvas().scene().removeItem(self.rubberband) if self.oldrubberband: self.iface.mapCanvas().scene().removeItem(self.oldrubberband) self.rubberband = None self.oldrubberband = None self.pressPos = None def canvasPressEvent(self, e): if not self.rubberband: return r = self.__canvasRect(self.rect) if e.button() == Qt.LeftButton and self.__canvasRect(self.rect).contains(e.pos()): self.oldrect = QRectF(self.rect) self.oldrubberband = QgsRubberBand(self.iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.oldrubberband.setToCanvasRectangle(self.__canvasRect(self.oldrect)) self.oldrubberband.setColor(QColor(127, 127, 255, 31)) self.pressPos = (e.x(), e.y()) self.iface.mapCanvas().setCursor(Qt.ClosedHandCursor) def canvasMoveEvent(self, e): if not self.pressPos: return mup = self.iface.mapCanvas().mapSettings().mapUnitsPerPixel() x = self.corner.x() + (e.x() - self.pressPos[0]) * mup y = self.corner.y() + (self.pressPos[1] - e.y()) * mup snaptol = 10 * mup # Left edge matches with old right if abs(x - (self.oldrect.x() + self.oldrect.width())) < snaptol: x = self.oldrect.x() + self.oldrect.width() # Right edge matches with old left elif abs(x + self.rect.width() - self.oldrect.x()) < snaptol: x = self.oldrect.x() - self.rect.width() # Left edge matches with old left elif abs(x - self.oldrect.x()) < snaptol: x = self.oldrect.x() # Bottom edge matches with old top if abs(y - (self.oldrect.y() + self.oldrect.height())) < snaptol: y = self.oldrect.y() + self.oldrect.height() # Top edge matches with old bottom elif abs(y + self.rect.height() - self.oldrect.y()) < snaptol: y = self.oldrect.y() - self.rect.height() # Bottom edge matches with old bottom elif abs(y - self.oldrect.y()) < snaptol: y = self.oldrect.y() self.rect = QRectF( x, y, self.rect.width(), self.rect.height() ) self.rubberband.setToCanvasRectangle(self.__canvasRect(self.rect)) def canvasReleaseEvent(self, e): if e.button() == Qt.LeftButton and self.pressPos: self.corner = QPointF(self.rect.x(), self.rect.y()) self.pressPos = None self.iface.mapCanvas().setCursor(Qt.OpenHandCursor) self.iface.mapCanvas().scene().removeItem(self.oldrubberband) self.oldrect = None self.oldrubberband = None self.mapitem.setExtent(QgsRectangle(self.rect)) def __canvasRect(self, rect): mtp = self.iface.mapCanvas().mapSettings().mapToPixel() p1 = mtp.transform(QgsPoint(rect.left(), rect.top())) p2 = mtp.transform(QgsPoint(rect.right(), rect.bottom())) return QRect(p1.x(), p1.y(), p2.x() - p1.x(), p2.y() - p1.y()) def __export(self): settings = QSettings() format = self.dialogui.comboBox_fileformat.itemData(self.dialogui.comboBox_fileformat.currentIndex()) filepath = QFileDialog.getSaveFileName( self.iface.mainWindow(), self.tr("Export Layout"), settings.value("/instantprint/lastfile", ""), format ) if not all(filepath): return # Ensure output filename has correct extension filename = os.path.splitext(filepath[0])[0] + "." + self.dialogui.comboBox_fileformat.currentText().lower() settings.setValue("/instantprint/lastfile", filepath[0]) if self.populateCompositionFz: self.populateCompositionFz(self.layoutView.composition()) success = False layout_name = self.dialogui.comboBox_layouts.currentText() layout_item = self.projectLayoutManager.layoutByName(layout_name) exporter = QgsLayoutExporter(layout_item) if filename[-3:].lower() == u"pdf": success = exporter.exportToPdf(filepath[0], QgsLayoutExporter.PdfExportSettings()) else: success = exporter.exportToImage(filepath[0], QgsLayoutExporter.ImageExportSettings()) if success != 0: QMessageBox.warning(self.iface.mainWindow(), self.tr("Export Failed"), self.tr("Failed to export the layout.")) def __print(self): layout_name = self.dialogui.comboBox_layouts.currentText() layout_item = self.projectLayoutManager.layoutByName(layout_name) actual_printer = QgsLayoutExporter(layout_item) printdialog = QPrintDialog(self.printer) if printdialog.exec_() != QDialog.Accepted: return success = actual_printer.print(self.printer, QgsLayoutExporter.PrintExportSettings()) if success != 0: QMessageBox.warning(self.iface.mainWindow(), self.tr("Print Failed"), self.tr("Failed to print the layout.")) def __reloadLayouts(self, removed=None): if not self.dialog.isVisible(): # Make it less likely to hit the issue outlined in https://github.com/qgis/QGIS/pull/1938 return self.dialogui.comboBox_layouts.blockSignals(True) prev = None if self.dialogui.comboBox_layouts.currentIndex() >= 0: prev = self.dialogui.comboBox_layouts.currentText() self.dialogui.comboBox_layouts.clear() active = 0 for layout in self.projectLayoutManager.layouts(): if layout != removed and layout.name(): cur = layout.name() self.dialogui.comboBox_layouts.addItem(cur, layout) if prev == cur: active = self.dialogui.comboBox_layouts.count() - 1 self.dialogui.comboBox_layouts.setCurrentIndex(-1) # Ensure setCurrentIndex below actually changes an index self.dialogui.comboBox_layouts.blockSignals(False) if self.dialogui.comboBox_layouts.count() > 0: self.dialogui.comboBox_layouts.setCurrentIndex(active) self.dialogui.comboBox_scale.setEnabled(True) self.exportButton.setEnabled(True) else: self.exportButton.setEnabled(False) self.dialogui.comboBox_scale.setEnabled(False) def __help(self): manualPath = os.path.join(os.path.dirname(__file__), "help", "documentation.pdf") QDesktopServices.openUrl(QUrl.fromLocalFile(manualPath)) def scaleFromString(self, scaleText): locale = QLocale() parts = [locale.toInt(part) for part in scaleText.split(":")] try: if len(parts) == 2 and parts[0][1] and parts[1][1] and parts[0][0] != 0 and parts[1][0] != 0: return float(parts[0][0]) / float(parts[1][0]) else: return None except ZeroDivisionError: return def check_scales(self): predefScalesStr = QSettings().value("Map/scales", PROJECT_SCALES).split(",") predefScales = [self.scaleFromString(scaleString) for scaleString in predefScalesStr] comboScalesStr = [self.dialogui.comboBox_scale.itemText(i) for i in range(self.dialogui.comboBox_scale.count())] comboScales = [self.scaleFromString(scaleString) for scaleString in comboScalesStr] currentScale = self.scaleFromString(self.dialogui.comboBox_scale.currentText()) if not currentScale: self.dialogui.comboBox_scale.lineEdit().setStyleSheet("background: #FF7777; color: #FFFFFF;") self.dialogui.addScale.setVisible(True) self.dialogui.addScale.setEnabled(False) self.dialogui.deleteScale.setVisible(False) else: self.dialogui.comboBox_scale.lineEdit().setStyleSheet("") if currentScale in comboScales: # If entry scale is already in the list, allow removing it unless it is a predefined scale self.dialogui.addScale.setVisible(False) self.dialogui.deleteScale.setVisible(True) self.dialogui.deleteScale.setEnabled(currentScale not in predefScales) else: # Otherwise, show button to add it self.dialogui.addScale.setVisible(True) self.dialogui.addScale.setEnabled(True) self.dialogui.deleteScale.setVisible(False)
class ArkMapToolInteractive(QgsMapTool): _active = False _dragging = False _panningEnabled = False _zoomingEnabled = False _zoomRubberBand = None #QgsRubberBand() _zoomRect = None # QRect() _snappingEnabled = False _snapper = None #QgsMapCanvasSnapper() _snappingMarker = None # QgsVertexMarker() _showSnappableVertices = False _snappableVertices = [] # [QgsPoint()] _snappableMarkers = [] # [QgsVertexMarker()] def __init__(self, canvas, snappingEnabled=False, showSnappableVertices=False): super(ArkMapToolInteractive, self).__init__(canvas) self._snappingEnabled = snappingEnabled self._showSnappableVertices = showSnappableVertices def __del__(self): if self._active: self.deactivate() def isActive(self): return self._active def activate(self): super(ArkMapToolInteractive, self).activate() self._active = True self._startSnapping() def deactivate(self): self._active = False if self._snappingEnabled: self._stopSnapping() if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None super(ArkMapToolInteractive, self).deactivate() def setAction(self, action): super(ArkMapToolInteractive, self).setAction(action) self.action().triggered.connect(self._activate) def _activate(self): self.canvas().setMapTool(self) def panningEnabled(self): return self._panningEnabled def setPanningEnabled(self, enabled): self._panningEnabled = enabled def zoomingEnabled(self): return self._zoomingEnabled def setZoomingEnabled(self, enabled): self._zoomingEnabled = enabled def snappingEnabled(self): return self._snappingEnabled def setSnappingEnabled(self, enabled): if (self._snappingEnabled == enabled): return self._snappingEnabled = enabled if not self._active: return if enabled: self._startSnapping() else: self._stopSnapping() def _startSnapping(self): self._snapper = QgsMapCanvasSnapper() self._snapper.setMapCanvas(self.canvas()) if self._showSnappableVertices: self._startSnappableVertices() def _stopSnapping(self): self._deleteSnappingMarker() self._snapper = None if self._showSnappableVertices: self._stopSnappableVertices() def showSnappableVertices(self): return self._showSnappableVertices def setShowSnappableVertices(self, show): if (self._showSnappableVertices == show): return self._showSnappableVertices = show if not self._active: return if show: self._startSnappableVertices() else: self._stopSnappableVertices() def _startSnappableVertices(self): self.canvas().layersChanged.connect(self._layersChanged) self.canvas().extentsChanged.connect(self._redrawSnappableMarkers) QgsProject.instance().snapSettingsChanged.connect(self._layersChanged) self._layersChanged() def _stopSnappableVertices(self): self._deleteSnappableMarkers() self._snappableLayers = [] self.canvas().layersChanged.disconnect(self._layersChanged) self.canvas().extentsChanged.disconnect(self._redrawSnappableMarkers) QgsProject.instance().snapSettingsChanged.disconnect(self._layersChanged) def canvasMoveEvent(self, e): super(ArkMapToolInteractive, self).canvasMoveEvent(e) if not self._active: return e.ignore() if (self._panningEnabled and e.buttons() & Qt.LeftButton): # Pan map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self.canvas().panAction(e) e.accept() elif (self._zoomingEnabled and e.buttons() & Qt.RightButton): # Zoom map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self._zoomRubberBand = QgsRubberBand(self.canvas(), QGis.Polygon) color = QColor(Qt.blue) color.setAlpha(63) self._zoomRubberBand.setColor(color) self._zoomRect = QRect(0, 0, 0, 0) self._zoomRect.setTopLeft(e.pos()) self._zoomRect.setBottomRight(e.pos()) if self._zoomRubberBand is not None: self._zoomRubberBand.setToCanvasRectangle(self._zoomRect) self._zoomRubberBand.show() e.accept() elif self._snappingEnabled: mapPoint, snapped = self._snapCursorPoint(e.pos()) if (snapped): self._createSnappingMarker(mapPoint) else: self._deleteSnappingMarker() def canvasReleaseEvent(self, e): super(ArkMapToolInteractive, self).canvasReleaseEvent(e) e.ignore() if (e.button() == Qt.LeftButton): if self._dragging: # Pan map mode self.canvas().panActionEnd(e.pos()) self.setCursor(capture_point_cursor) self._dragging = False e.accept() elif (e.button() == Qt.RightButton): if self._dragging: # Zoom mode self._zoomRect.setBottomRight(e.pos()) if (self._zoomRect.topLeft() != self._zoomRect.bottomRight()): coordinateTransform = self.canvas().getCoordinateTransform() ll = coordinateTransform.toMapCoordinates(self._zoomRect.left(), self._zoomRect.bottom()) ur = coordinateTransform.toMapCoordinates(self._zoomRect.right(), self._zoomRect.top()) r = QgsRectangle() r.setXMinimum(ll.x()) r.setYMinimum(ll.y()) r.setXMaximum(ur.x()) r.setYMaximum(ur.y()) r.normalize() if (r.width() != 0 and r.height() != 0): self.canvas().setExtent(r) self.canvas().refresh() self._dragging = False if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None e.accept() def keyPressEvent(self, e): super(ArkMapToolInteractive, self).keyPressEvent(e) if (e.key() == Qt.Key_Escape): self.canvas().unsetMapTool(self) e.accept() def _snapCursorPoint(self, cursorPoint): res, snapResults = self._snapper.snapToBackgroundLayers(cursorPoint) if (res != 0 or len(snapResults) < 1): return self.toMapCoordinates(cursorPoint), False else: # Take a copy as QGIS will delete the result! snappedVertex = QgsPoint(snapResults[0].snappedVertex) return snappedVertex, True def _createSnappingMarker(self, snapPoint): if (self._snappingMarker is None): self._snappingMarker = QgsVertexMarker(self.canvas()) self._snappingMarker.setIconType(QgsVertexMarker.ICON_CROSS) self._snappingMarker.setColor(Qt.magenta) self._snappingMarker.setPenWidth(3) self._snappingMarker.setCenter(snapPoint) def _deleteSnappingMarker(self): if (self._snappingMarker is not None): self.canvas().scene().removeItem(self._snappingMarker) self._snappingMarker = None def _createSnappableMarkers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return extent = self.canvas().extent() for vertex in self._snappableVertices.asMultiPoint(): if (extent.contains(vertex)): marker = QgsVertexMarker(self.canvas()) marker.setIconType(QgsVertexMarker.ICON_X) marker.setColor(Qt.gray) marker.setPenWidth(1) marker.setCenter(vertex) self._snappableMarkers.append(marker) def _deleteSnappableMarkers(self): for marker in self._snappableMarkers: self.canvas().scene().removeItem(marker) del self._snappableMarkers[:] def _layersChanged(self): if (not self._showSnappableVertices or not self._snappingEnabled): return self._buildSnappableLayers() self._deleteSnappableMarkers() self._createSnappableMarkers() def _redrawSnappableMarkers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return self._deleteSnappableMarkers() self._createSnappableMarkers() def _buildSnappableLayers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return vertices = [] for layer in self.canvas().layers(): ok, enabled, type, units, tolerance, avoid = QgsProject.instance().snapSettingsForLayer(layer.id()) if (ok and enabled and not layer.isEditable()): for feature in layer.getFeatures(): geometry = feature.geometry() if geometry is None: pass elif geometry.type() == QGis.Point: vertices.extend([geometry.asPoint()]) elif geometry.type() == QGis.Line: vertices.extend(geometry.asPolyline()) elif geometry.type() == QGis.Polygon: lines = geometry.asPolygon() for line in lines: vertices.extend(line) self._snappableVertices = QgsGeometry.fromMultiPoint(vertices) self._snappableVertices.simplify(0)
class ArkMapToolInteractive(QgsMapTool): _active = False _dragging = False _panningEnabled = False _zoomingEnabled = False _zoomRubberBand = None #QgsRubberBand() _zoomRect = None # QRect() _snappingEnabled = False _snapper = None #QgsMapCanvasSnapper() _snappingMarker = None # QgsVertexMarker() _showSnappableVertices = False _snappableVertices = [] # [QgsPoint()] _snappableMarkers = [] # [QgsVertexMarker()] def __init__(self, canvas, snappingEnabled=False, showSnappableVertices=False): super(ArkMapToolInteractive, self).__init__(canvas) self._snappingEnabled = snappingEnabled self._showSnappableVertices = showSnappableVertices def __del__(self): if self._active: self.deactivate() def isActive(self): return self._active def activate(self): super(ArkMapToolInteractive, self).activate() self._active = True self._startSnapping() def deactivate(self): self._active = False if self._snappingEnabled: self._stopSnapping() if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None super(ArkMapToolInteractive, self).deactivate() def setAction(self, action): super(ArkMapToolInteractive, self).setAction(action) self.action().triggered.connect(self._activate) def _activate(self): self.canvas().setMapTool(self) def panningEnabled(self): return self._panningEnabled def setPanningEnabled(self, enabled): self._panningEnabled = enabled def zoomingEnabled(self): return self._zoomingEnabled def setZoomingEnabled(self, enabled): self._zoomingEnabled = enabled def snappingEnabled(self): return self._snappingEnabled def setSnappingEnabled(self, enabled): if (self._snappingEnabled == enabled): return self._snappingEnabled = enabled if not self._active: return if enabled: self._startSnapping() else: self._stopSnapping() def _startSnapping(self): self._snapper = QgsMapCanvasSnapper() self._snapper.setMapCanvas(self.canvas()) if self._showSnappableVertices: self._startSnappableVertices() def _stopSnapping(self): self._deleteSnappingMarker() self._snapper = None if self._showSnappableVertices: self._stopSnappableVertices() def showSnappableVertices(self): return self._showSnappableVertices def setShowSnappableVertices(self, show): if (self._showSnappableVertices == show): return self._showSnappableVertices = show if not self._active: return if show: self._startSnappableVertices() else: self._stopSnappableVertices() def _startSnappableVertices(self): self.canvas().layersChanged.connect(self._layersChanged) self.canvas().extentsChanged.connect(self._redrawSnappableMarkers) QgsProject.instance().snapSettingsChanged.connect(self._layersChanged) self._layersChanged() def _stopSnappableVertices(self): self._deleteSnappableMarkers() self._snappableLayers = [] self.canvas().layersChanged.disconnect(self._layersChanged) self.canvas().extentsChanged.disconnect(self._redrawSnappableMarkers) QgsProject.instance().snapSettingsChanged.disconnect(self._layersChanged) def canvasMoveEvent(self, e): super(ArkMapToolInteractive, self).canvasMoveEvent(e) if not self._active: return e.ignore() if (self._panningEnabled and e.buttons() & Qt.LeftButton): # Pan map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self.canvas().panAction(e) e.accept() elif (self._zoomingEnabled and e.buttons() & Qt.RightButton): # Zoom map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self._zoomRubberBand = QgsRubberBand(self.canvas(), QGis.Polygon) color = QColor(Qt.blue) color.setAlpha(63) self._zoomRubberBand.setColor(color) self._zoomRect = QRect(0, 0, 0, 0) self._zoomRect.setTopLeft(e.pos()) self._zoomRect.setBottomRight(e.pos()) if self._zoomRubberBand is not None: self._zoomRubberBand.setToCanvasRectangle(self._zoomRect) self._zoomRubberBand.show() e.accept() elif self._snappingEnabled: mapPoint, snapped = self._snapCursorPoint(e.pos()) if (snapped): self._createSnappingMarker(mapPoint) else: self._deleteSnappingMarker() def canvasReleaseEvent(self, e): super(ArkMapToolInteractive, self).canvasReleaseEvent(e) e.ignore() if (e.button() == Qt.LeftButton): if self._dragging: # Pan map mode self.canvas().panActionEnd(e.pos()) self.setCursor(capture_point_cursor) self._dragging = False e.accept() elif (e.button() == Qt.RightButton): if self._dragging: # Zoom mode self._zoomRect.setBottomRight(e.pos()) if (self._zoomRect.topLeft() != self._zoomRect.bottomRight()): coordinateTransform = self.canvas().getCoordinateTransform() ll = coordinateTransform.toMapCoordinates(self._zoomRect.left(), self._zoomRect.bottom()) ur = coordinateTransform.toMapCoordinates(self._zoomRect.right(), self._zoomRect.top()) r = QgsRectangle() r.setXMinimum(ll.x()) r.setYMinimum(ll.y()) r.setXMaximum(ur.x()) r.setYMaximum(ur.y()) r.normalize() if (r.width() != 0 and r.height() != 0): self.canvas().setExtent(r) self.canvas().refresh() self._dragging = False if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None e.accept() def keyPressEvent(self, e): super(ArkMapToolInteractive, self).keyPressEvent(e) if (e.key() == Qt.Key_Escape): self.canvas().unsetMapTool(self) e.accept() def _snapCursorPoint(self, cursorPoint): res, snapResults = self._snapper.snapToBackgroundLayers(cursorPoint) if (res != 0 or len(snapResults) < 1): return self.toMapCoordinates(cursorPoint), False else: # Take a copy as QGIS will delete the result! snappedVertex = QgsPoint(snapResults[0].snappedVertex) return snappedVertex, True def _createSnappingMarker(self, snapPoint): if (self._snappingMarker is None): self._snappingMarker = QgsVertexMarker(self.canvas()) self._snappingMarker.setIconType(QgsVertexMarker.ICON_CROSS) self._snappingMarker.setColor(Qt.magenta) self._snappingMarker.setPenWidth(3) self._snappingMarker.setCenter(snapPoint) def _deleteSnappingMarker(self): if (self._snappingMarker is not None): self.canvas().scene().removeItem(self._snappingMarker) self._snappingMarker = None def _createSnappableMarkers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return extent = self.canvas().extent() for vertex in self._snappableVertices.asMultiPoint(): if (extent.contains(vertex)): marker = QgsVertexMarker(self.canvas()) marker.setIconType(QgsVertexMarker.ICON_X) marker.setColor(Qt.gray) marker.setPenWidth(1) marker.setCenter(vertex) self._snappableMarkers.append(marker) def _deleteSnappableMarkers(self): for marker in self._snappableMarkers: self.canvas().scene().removeItem(marker) del self._snappableMarkers[:] def _layersChanged(self): if (not self._showSnappableVertices or not self._snappingEnabled): return self._buildSnappableLayers() self._deleteSnappableMarkers() self._createSnappableMarkers() def _redrawSnappableMarkers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return self._deleteSnappableMarkers() self._createSnappableMarkers() def _buildSnappableLayers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return vertices = [] for layer in self.canvas().layers(): ok, enabled, type, units, tolerance, avoid = QgsProject.instance().snapSettingsForLayer(layer.id()) if (ok and enabled and not layer.isEditable()): for feature in layer.getFeatures(): geometry = feature.geometry() if geometry is None: pass elif geometry.type() == QGis.Point: vertices.extend([geometry.asPoint()]) elif geometry.type() == QGis.Line: vertices.extend(geometry.asPolyline()) elif geometry.type() == QGis.Polygon: lines = geometry.asPolygon() for line in lines: vertices.extend(line) self._snappableVertices = QgsGeometry.fromMultiPoint(vertices) self._snappableVertices.simplify(0)
class MapToolInteractive(QgsMapTool): """Tool to interact with map, including panning, zooming, and snapping""" def __init__(self, canvas, snappingEnabled=False): super(MapToolInteractive, self).__init__(canvas) self._active = False self._dragging = False self._panningEnabled = False self._zoomingEnabled = False self._zoomRubberBand = None # QgsRubberBand() self._zoomRect = None # QRect() self._snappingEnabled = snappingEnabled self._snapper = None # QgsMapCanvasSnapper() self._snappingMarker = None # QgsVertexMarker() def __del__(self): if self._active: self.deactivate() def isActive(self): return self._active def activate(self): super(MapToolInteractive, self).activate() self._active = True self._startSnapping() def deactivate(self): self._active = False if self._snappingEnabled: self._stopSnapping() if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None super(MapToolInteractive, self).deactivate() def setAction(self, action): super(MapToolInteractive, self).setAction(action) self.action().triggered.connect(self._activate) def _activate(self): self.canvas().setMapTool(self) def panningEnabled(self): return self._panningEnabled def setPanningEnabled(self, enabled): self._panningEnabled = enabled def zoomingEnabled(self): return self._zoomingEnabled def setZoomingEnabled(self, enabled): self._zoomingEnabled = enabled def snappingEnabled(self): return self._snappingEnabled def setSnappingEnabled(self, enabled): if (self._snappingEnabled == enabled): return self._snappingEnabled = enabled if not self._active: return if enabled: self._startSnapping() else: self._stopSnapping() def _startSnapping(self): self._snapper = QgsMapCanvasSnapper() self._snapper.setMapCanvas(self.canvas()) def _stopSnapping(self): self._deleteSnappingMarker() self._snapper = None def canvasMoveEvent(self, e): super(MapToolInteractive, self).canvasMoveEvent(e) if not self._active: return e.ignore() if (self._panningEnabled and e.buttons() & Qt.LeftButton): # Pan map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self.canvas().panAction(e) e.accept() elif (self._zoomingEnabled and e.buttons() & Qt.RightButton): # Zoom map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self._zoomRubberBand = QgsRubberBand(self.canvas(), QGis.Polygon) color = QColor(Qt.blue) color.setAlpha(63) self._zoomRubberBand.setColor(color) self._zoomRect = QRect(0, 0, 0, 0) self._zoomRect.setTopLeft(e.pos()) self._zoomRect.setBottomRight(e.pos()) if self._zoomRubberBand is not None: self._zoomRubberBand.setToCanvasRectangle(self._zoomRect) self._zoomRubberBand.show() e.accept() elif self._snappingEnabled: mapPoint, mapPointV2, snapped = self._snapCursorPoint(e.pos()) if (snapped): self._createSnappingMarker(mapPoint) else: self._deleteSnappingMarker() def canvasReleaseEvent(self, e): super(MapToolInteractive, self).canvasReleaseEvent(e) e.ignore() if (e.button() == Qt.LeftButton): if self._dragging: # Pan map mode self.canvas().panActionEnd(e.pos()) self.setCursor(CapturePointCursor) self._dragging = False e.accept() elif (e.button() == Qt.RightButton): if self._dragging: # Zoom mode self._zoomRect.setBottomRight(e.pos()) if (self._zoomRect.topLeft() != self._zoomRect.bottomRight()): coordinateTransform = self.canvas().getCoordinateTransform( ) ll = coordinateTransform.toMapCoordinates( self._zoomRect.left(), self._zoomRect.bottom()) ur = coordinateTransform.toMapCoordinates( self._zoomRect.right(), self._zoomRect.top()) r = QgsRectangle() r.setXMinimum(ll.x()) r.setYMinimum(ll.y()) r.setXMaximum(ur.x()) r.setYMaximum(ur.y()) r.normalize() if (r.width() != 0 and r.height() != 0): self.canvas().setExtent(r) self.canvas().refresh() self._dragging = False if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None e.accept() def keyPressEvent(self, e): super(MapToolInteractive, self).keyPressEvent(e) if (e.key() == Qt.Key_Escape): self.canvas().unsetMapTool(self) e.accept() def _snapCursorPoint(self, cursorPoint): res, snapResults = self._snapper.snapToBackgroundLayers(cursorPoint) if (res != 0 or len(snapResults) < 1): clicked = self.toMapCoordinates(cursorPoint) clickedV2 = QgsPointV2(clicked) return clicked, clickedV2, False else: # Take a copy as QGIS will delete the result! snapped = QgsPoint(snapResults[0].snappedVertex) snappedV2 = QgsPointV2(snapped) return snapped, snappedV2, True def _createSnappingMarker(self, snapPoint): if (self._snappingMarker is None): self._snappingMarker = QgsVertexMarker(self.canvas()) self._snappingMarker.setIconType(QgsVertexMarker.ICON_CROSS) self._snappingMarker.setColor(Qt.magenta) self._snappingMarker.setPenWidth(3) self._snappingMarker.setCenter(snapPoint) def _deleteSnappingMarker(self): if (self._snappingMarker is not None): self.canvas().scene().removeItem(self._snappingMarker) self._snappingMarker = None