class SplitMapTool(QgsMapToolEdit): def __init__(self, canvas, layer, actionMoveVertices, actionAddVertices, actionRemoveVertices, actionMoveSegment, actionLineClose, actionLineOpen, actionMoveLine): super(SplitMapTool, self).__init__(canvas) self.canvas = canvas self.scene = canvas.scene() self.layer = layer self.actionMoveVertices = actionMoveVertices self.actionAddVertices = actionAddVertices self.actionRemoveVertices = actionRemoveVertices self.actionMoveSegment = actionMoveSegment self.actionLineClose = actionLineClose self.actionLineOpen = actionLineOpen self.actionMoveLine = actionMoveLine self.initialize() def initialize(self): try: self.canvas.renderStarting.disconnect(self.mapCanvasChanged) except: pass self.canvas.renderStarting.connect(self.mapCanvasChanged) try: self.layer.editingStopped.disconnect(self.stopCapturing) except: pass self.layer.editingStopped.connect(self.stopCapturing) self.selectedFeatures = self.layer.selectedFeatures() self.rubberBand = None self.tempRubberBand = None self.capturedPoints = [] self.capturing = False self.setCursor(Qt.CrossCursor) self.proj = QgsProject.instance() self.labels = [] self.vertices = [] self.calculator = QgsDistanceArea() self.calculator.setSourceCrs(self.layer.dataProvider().crs(), QgsProject.instance().transformContext()) self.calculator.setEllipsoid( self.layer.dataProvider().crs().ellipsoidAcronym()) self.drawingLine = False self.movingVertices = False self.addingVertices = False self.removingVertices = False self.movingSegment = False self.movingLine = False self.showingVertices = False self.movingVertex = -1 self.movingSegm = -1 self.movingLineInitialPoint = None self.lineClosed = False def restoreAction(self): self.addingVertices = False self.removingVertices = False self.movingVertices = False self.movingSegment = False self.movingLine = False self.showingVertices = False self.drawingLine = True self.movingVertex = -1 self.movingLineInitialPoint = None self.deleteVertices() self.redrawRubberBand() self.redrawTempRubberBand() self.canvas.scene().addItem(self.tempRubberBand) self.redrawActions() def mapCanvasChanged(self): self.redrawAreas() if self.showingVertices: self.redrawVertices() def canvasMoveEvent(self, event): if self.drawingLine and not self.lineClosed: if self.tempRubberBand != None and self.capturing: mapPoint = self.toMapCoordinates(event.pos()) self.tempRubberBand.movePoint(mapPoint) self.redrawAreas(event.pos()) if self.movingVertices and self.movingVertex >= 0: layerPoint = self.toLayerCoordinates(self.layer, event.pos()) self.capturedPoints[self.movingVertex] = layerPoint if self.lineClosed and self.movingVertex == 0: self.capturedPoints[len(self.capturedPoints) - 1] = layerPoint self.redrawRubberBand() self.redrawVertices() self.redrawAreas() if self.movingSegment and self.movingSegm >= 0: currentPoint = self.toLayerCoordinates(self.layer, event.pos()) distance = self.distancePoint(currentPoint, self.movingLineInitialPoint) bearing = self.movingLineInitialPoint.azimuth(currentPoint) self.capturedPoints[self.movingSegm] = self.projectPoint( self.capturedPoints[self.movingSegm], distance, bearing) self.capturedPoints[self.movingSegm + 1] = self.projectPoint( self.capturedPoints[self.movingSegm + 1], distance, bearing) if self.lineClosed: if self.movingSegm == 0: self.capturedPoints[ len(self.capturedPoints) - 1] = self.projectPoint( self.capturedPoints[len(self.capturedPoints) - 1], distance, bearing) elif self.movingSegm == len(self.capturedPoints) - 2: self.capturedPoints[0] = self.projectPoint( self.capturedPoints[0], distance, bearing) self.redrawRubberBand() self.redrawVertices() self.redrawAreas() self.movingLineInitialPoint = currentPoint if self.movingLine and self.movingLineInitialPoint != None: currentPoint = self.toLayerCoordinates(self.layer, event.pos()) distance = self.distancePoint(currentPoint, self.movingLineInitialPoint) bearing = self.movingLineInitialPoint.azimuth(currentPoint) for i in range(len(self.capturedPoints)): self.capturedPoints[i] = self.projectPoint( self.capturedPoints[i], distance, bearing) self.redrawRubberBand() self.redrawAreas() self.movingLineInitialPoint = currentPoint def projectPoint(self, point, distance, bearing): rads = bearing * pi / 180.0 dx = distance * sin(rads) dy = distance * cos(rads) return QgsPointXY(point.x() + dx, point.y() + dy) def redrawAreas(self, mousePos=None): self.deleteLabels() if self.capturing and len(self.capturedPoints) > 0: for i in range(len(self.selectedFeatures)): geometry = QgsGeometry(self.selectedFeatures[i].geometry()) movingPoints = list(self.capturedPoints) if mousePos != None: movingPoints.append( self.toLayerCoordinates(self.layer, mousePos)) result, newGeometries, topoTestPoints = geometry.splitGeometry( movingPoints, self.proj.topologicalEditing()) self.addLabel(geometry) if newGeometries != None and len(newGeometries) > 0: for i in range(len(newGeometries)): self.addLabel(newGeometries[i]) def addLabel(self, geometry): area = self.calculator.measureArea(geometry) labelPoint = geometry.pointOnSurface().vertexAt(0) label = QGraphicsTextItem("%.2f" % round(area, 2)) label.setHtml( "<div style=\"color:#ffffff;background:#111111;padding:5px\">" + "%.2f" % round(area, 2) + " " + areaUnits[self.calculator.areaUnits()] + "</div>") point = self.toMapCoordinatesV2(self.layer, labelPoint) label.setPos(self.toCanvasCoordinates(QgsPointXY(point.x(), point.y()))) self.scene.addItem(label) self.labels.append(label) def deleteLabels(self): for i in range(len(self.labels)): self.scene.removeItem(self.labels[i]) self.labels = [] def canvasPressEvent(self, event): if self.movingVertices: for i in range(len(self.capturedPoints)): point = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex = self.toCanvasCoordinates( QgsPointXY(point.x(), point.y())) if self.distancePoint(event.pos(), currentVertex) <= maxDistanceHitTest: self.movingVertex = i break if self.movingSegment: for i in range(len(self.capturedPoints) - 1): vertex1 = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex1 = self.toCanvasCoordinates( QgsPointXY(vertex1.x(), vertex1.y())) vertex2 = self.toMapCoordinates(self.layer, self.capturedPoints[i + 1]) currentVertex2 = self.toCanvasCoordinates( QgsPointXY(vertex2.x(), vertex2.y())) if self.distancePointLine( event.pos().x(), event.pos().y(), currentVertex1.x(), currentVertex1.y(), currentVertex2.x(), currentVertex2.y()) <= maxDistanceHitTest: self.movingSegm = i break self.movingLineInitialPoint = self.toLayerCoordinates( self.layer, event.pos()) def distancePoint(self, eventPos, vertexPos): return sqrt((eventPos.x() - vertexPos.x())**2 + (eventPos.y() - vertexPos.y())**2) def canvasReleaseEvent(self, event): if self.movingVertices or self.movingSegment or self.movingLine: if event.button() == Qt.RightButton: self.finishOperation() elif self.addingVertices: if event.button() == Qt.LeftButton: self.addVertex(event.pos()) elif event.button() == Qt.RightButton: self.finishOperation() elif self.removingVertices: if event.button() == Qt.LeftButton: self.removeVertex(event.pos()) elif event.button() == Qt.RightButton: self.finishOperation() else: if event.button() == Qt.LeftButton: if not self.lineClosed: if not self.capturing: self.startCapturing() self.addEndingVertex(event.pos()) elif event.button() == Qt.RightButton: self.finishOperation() self.movingVertex = -1 self.movingSegm = -1 self.movingLineInitialPoint = None self.redrawActions() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.stopCapturing() if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete: self.removeLastVertex() if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: self.finishOperation() event.accept() self.redrawActions() def finishOperation(self): self.doSplit() self.stopCapturing() self.initialize() self.startCapturing() def doSplit(self): if self.capturedPoints != None: self.layer.splitFeatures(self.capturedPoints, self.proj.topologicalEditing()) def startCapturing(self): self.prepareRubberBand() self.prepareTempRubberBand() self.drawingLine = True self.capturing = True self.redrawActions() def prepareRubberBand(self): color = QColor("red") color.setAlphaF(0.78) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.rubberBand.setWidth(1) self.rubberBand.setColor(color) self.rubberBand.show() def prepareTempRubberBand(self): color = QColor("red") color.setAlphaF(0.78) self.tempRubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.tempRubberBand.setWidth(1) self.tempRubberBand.setColor(color) self.tempRubberBand.setLineStyle(Qt.DotLine) self.tempRubberBand.show() def redrawRubberBand(self): self.canvas.scene().removeItem(self.rubberBand) self.prepareRubberBand() for i in range(len(self.capturedPoints)): point = self.capturedPoints[i] if point.__class__ == QgsPoint: vertexCoord = self.toMapCoordinatesV2(self.layer, self.capturedPoints[i]) vertexCoord = QgsPointXY(vertexCoord.x(), vertexCoord.y()) else: vertexCoord = self.toMapCoordinates(self.layer, self.capturedPoints[i]) self.rubberBand.addPoint(vertexCoord) def redrawTempRubberBand(self): if self.tempRubberBand != None: self.tempRubberBand.reset(QgsWkbTypes.LineGeometry) self.tempRubberBand.addPoint( self.toMapCoordinates( self.layer, self.capturedPoints[len(self.capturedPoints) - 1])) def stopCapturing(self): self.deleteLabels() self.deleteVertices() if self.rubberBand: self.canvas.scene().removeItem(self.rubberBand) self.rubberBand = None if self.tempRubberBand: self.canvas.scene().removeItem(self.tempRubberBand) self.tempRubberBand = None self.drawingLine = False self.movingVertices = False self.showingVertices = False self.capturing = False self.capturedPoints = [] self.canvas.refresh() self.redrawActions() def addEndingVertex(self, canvasPoint): mapPoint = self.toMapCoordinates(canvasPoint) layerPoint = self.toLayerCoordinates(self.layer, canvasPoint) self.rubberBand.addPoint(mapPoint) self.capturedPoints.append(layerPoint) self.tempRubberBand.reset(QgsWkbTypes.LineGeometry) self.tempRubberBand.addPoint(mapPoint) def removeLastVertex(self): if not self.capturing: return rubberBandSize = self.rubberBand.numberOfVertices() tempRubberBandSize = self.tempRubberBand.numberOfVertices() numPoints = len(self.capturedPoints) if rubberBandSize < 1 or numPoints < 1: return self.rubberBand.removePoint(-1) if rubberBandSize > 1: if tempRubberBandSize > 1: point = self.rubberBand.getPoint(0, rubberBandSize - 2) self.tempRubberBand.movePoint(tempRubberBandSize - 2, point) else: self.tempRubberBand.reset(self.bandType()) del self.capturedPoints[-1] def addVertex(self, pos): newCapturedPoints = [] for i in range(len(self.capturedPoints) - 1): newCapturedPoints.append(self.capturedPoints[i]) vertex1 = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex1 = self.toCanvasCoordinates( QgsPointXY(vertex1.x(), vertex1.y())) vertex2 = self.toMapCoordinates(self.layer, self.capturedPoints[i + 1]) currentVertex2 = self.toCanvasCoordinates( QgsPointXY(vertex2.x(), vertex2.y())) distance = self.distancePointLine(pos.x(), pos.y(), currentVertex1.x(), currentVertex1.y(), currentVertex2.x(), currentVertex2.y()) if distance <= maxDistanceHitTest: layerPoint = self.toLayerCoordinates(self.layer, pos) newCapturedPoints.append(layerPoint) newCapturedPoints.append(self.capturedPoints[len(self.capturedPoints) - 1]) self.capturedPoints = newCapturedPoints self.redrawRubberBand() self.redrawVertices() self.redrawAreas() self.redrawActions() def removeVertex(self, pos): deletedFirst = False deletedLast = False newCapturedPoints = [] for i in range(len(self.capturedPoints)): vertex = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex = self.toCanvasCoordinates( QgsPointXY(vertex.x(), vertex.y())) if not self.distancePoint(pos, currentVertex) <= maxDistanceHitTest: newCapturedPoints.append(self.capturedPoints[i]) elif i == 0: deletedFirst = True elif i == len(self.capturedPoints) - 1: deletedLast = True self.capturedPoints = newCapturedPoints if deletedFirst and deletedLast: self.lineClosed = False self.redrawRubberBand() self.redrawVertices() self.redrawAreas() self.redrawActions() if len(self.capturedPoints) <= 2: self.stopRemovingVertices() def startMovingVertices(self): self.stopMovingLine() self.stopAddingVertices() self.stopRemovingVertices() self.stopMovingSegment() self.actionMoveVertices.setChecked(True) self.movingVertices = True self.showingVertices = True self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopMovingVertices(self): self.movingVertices = False self.actionMoveVertices.setChecked(False) self.restoreAction() def startAddingVertices(self): self.stopMovingVertices() self.stopRemovingVertices() self.stopMovingLine() self.stopMovingSegment() self.actionAddVertices.setChecked(True) self.addingVertices = True self.showingVertices = True self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopAddingVertices(self): self.addVertices = False self.actionAddVertices.setChecked(False) self.restoreAction() def startRemovingVertices(self): self.stopMovingVertices() self.stopAddingVertices() self.stopMovingLine() self.stopMovingSegment() self.actionRemoveVertices.setChecked(True) self.removingVertices = True self.showingVertices = True self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopRemovingVertices(self): self.removingVertices = False self.actionRemoveVertices.setChecked(False) self.restoreAction() def startMovingSegment(self): self.stopMovingVertices() self.stopMovingLine() self.stopAddingVertices() self.stopRemovingVertices() self.actionMoveSegment.setChecked(True) self.movingSegment = True self.showingVertices = False self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopMovingSegment(self): self.movingSegment = False self.actionMoveSegment.setChecked(False) self.restoreAction() def startMovingLine(self): self.stopMovingVertices() self.stopAddingVertices() self.stopRemovingVertices() self.stopMovingSegment() self.actionMoveLine.setChecked(True) self.movingLine = True self.showingVertices = False self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawAreas() self.redrawActions() def stopMovingLine(self): self.actionMoveLine.setChecked(False) self.restoreAction() def lineClose(self): self.lineClosed = True self.capturedPoints.append(self.capturedPoints[0]) self.redrawRubberBand() self.redrawTempRubberBand() self.redrawAreas() self.redrawActions() def lineOpen(self): self.lineClosed = False del self.capturedPoints[-1] self.redrawRubberBand() self.redrawTempRubberBand() self.redrawAreas() self.redrawActions() def showVertices(self): for i in range(len(self.capturedPoints)): vertexc = self.toMapCoordinates(self.layer, self.capturedPoints[i]) vertexCoords = self.toCanvasCoordinates( QgsPointXY(vertexc.x(), vertexc.y())) if i == self.movingVertex: vertex = self.scene.addRect(vertexCoords.x() - 5, vertexCoords.y() - 5, 10, 10, QPen(QColor("green")), QBrush(QColor("green"))) self.vertices.append(vertex) elif i == len(self.capturedPoints ) - 1 and self.movingVertex == 0 and self.lineClosed: vertex = self.scene.addRect(vertexCoords.x() - 5, vertexCoords.y() - 5, 10, 10, QPen(QColor("green")), QBrush(QColor("green"))) self.vertices.append(vertex) else: vertex = self.scene.addRect(vertexCoords.x() - 4, vertexCoords.y() - 4, 8, 8, QPen(QColor("red")), QBrush(QColor("red"))) self.vertices.append(vertex) def deleteVertices(self): for i in range(len(self.vertices)): self.scene.removeItem(self.vertices[i]) self.vertices = [] def lineMagnitude(self, x1, y1, x2, y2): return sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2)) def distancePointLine(self, px, py, x1, y1, x2, y2): magnitude = self.lineMagnitude(x1, y1, x2, y2) if magnitude < 0.00000001: distance = 9999 return distance u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) u = u1 / (magnitude * magnitude) if (u < 0.00001) or (u > 1): ix = self.lineMagnitude(px, py, x1, y1) iy = self.lineMagnitude(px, py, x2, y2) if ix > iy: distance = iy else: distance = ix else: ix = x1 + u * (x2 - x1) iy = y1 + u * (y2 - y1) distance = self.lineMagnitude(px, py, ix, iy) return distance def redrawVertices(self): self.deleteVertices() self.showVertices() def redrawActions(self): self.redrawActionMoveVertices() self.redrawActionAddVertices() self.redrawActionRemoveVertices() self.redrawActionMoveSegment() self.redrawActionLineClose() self.redrawActionLineOpen() self.redrawActionMoveLine() def redrawActionMoveVertices(self): self.actionMoveVertices.setEnabled(False) if len(self.capturedPoints) > 0: self.actionMoveVertices.setEnabled(True) def redrawActionAddVertices(self): self.actionAddVertices.setEnabled(False) if len(self.capturedPoints) >= 2: self.actionAddVertices.setEnabled(True) def redrawActionRemoveVertices(self): self.actionRemoveVertices.setEnabled(False) if len(self.capturedPoints) > 2: self.actionRemoveVertices.setEnabled(True) def redrawActionMoveSegment(self): self.actionMoveSegment.setEnabled(False) if len(self.capturedPoints) > 2: self.actionMoveSegment.setEnabled(True) def redrawActionLineClose(self): self.actionLineClose.setEnabled(False) if not self.lineClosed and len(self.capturedPoints) >= 3: self.actionLineClose.setEnabled(True) def redrawActionLineOpen(self): self.actionLineOpen.setEnabled(False) if self.lineClosed: self.actionLineOpen.setEnabled(True) def redrawActionMoveLine(self): self.actionMoveLine.setEnabled(False) if len(self.capturedPoints) > 0: self.actionMoveLine.setEnabled(True)
class VertexTracerTool(QgsMapTool): traceFound = pyqtSignal(QgsGeometry) def __init__(self, canvas): # some stuff we need from qgis QgsMapTool.__init__(self, canvas) self.canvas = canvas self.snapper = QgsSnappingUtils() # some stuff to control our state self.mCtrl = False self.started = False self.firstTimeOnSegment = True self.lastPointMustStay = False # some stuff to put our temp output self.lastPoint = None self.rb = None self.isPolygon = False # our own fancy cursor self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #00FF00", "+ c #FFFFFF", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) # we need to know, if ctrl-key is pressed def keyPressEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = True # and also if ctrl-key is released def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = False # remove the last added point when the delete key is pressed if event.key() == Qt.Key_Backspace: self.rb.removeLastPoint() def canvasPressEvent(self, event): # on left click, we add a point if event.button() == 1: layer = self.canvas.currentLayer() # if it is the start of a new trace, set the rubberband up if not self.started: if layer.geometryType() == 1: self.isPolygon = False if layer.geometryType() == 2: self.isPolygon = True self.rb = QgsRubberBand(self.canvas, layer.geometryType()) self.rb.setColor(QColor(255, 0, 0)) # declare, that are we going to work, now! self.started = True if layer is not None: x = event.pos().x() y = event.pos().y() selPoint = QPoint(x, y) # try to get something snapped (retval, result) = self.snapper.snapToBackgroundLayers(selPoint) # the point we want to have, is either from snapping result if result != []: point = result[0].snappedVertex # if we snapped something, it's either a vertex if result[0].snappedVertexNr != -1: self.firstTimeOnSegment = True # or a point on a segment, so we have to declare, that a point on segment is found else: self.firstTimeOnSegment = False # or its some point from out in the wild else: point = QgsMapToPixel.toMapCoordinates( self.canvas.getCoordinateTransform(), x, y) self.firstTimeOnSegment = True # bring the rubberband to the cursor i.e. the clicked point self.rb.movePoint(point) # and set a new point to go on with self.appendPoint(point) # try to remember that this point was on purpose i.e. clicked by the user self.lastPointMustStay = True self.firstTimeOnSegment = True def canvasMoveEvent(self, event): # QgsMessageLog.logMessage('fts: '+str(self.firstTimeOnSegment), 'state', 0) # QgsMessageLog.logMessage('lpc: '+str(self.lastPointMustStay), 'state', 0) if self.started: # Get the click x = event.pos().x() y = event.pos().y() eventPoint = QPoint(x, y) # only if the ctrl key is pressed if self.mCtrl: layer = self.canvas.currentLayer() if layer is not None: (retval, result) = self.snapper.snapToBackgroundLayers(eventPoint) # so if we have found a snapping if result != []: # pydevd.settrace() # that's the snapped point point = QgsPoint(result[0].snappedVertex) # if it is a vertex, not a point on a segment if result[0].snappedVertexNr != -1: self.rb.movePoint(point) self.appendPoint(point) self.lastPointMustStay = True # the next point found, may be on a segment self.firstTimeOnSegment = True # we are on a segment else: self.rb.movePoint(point) # if we are on a new segment, we add the point in any case if self.firstTimeOnSegment: self.appendPoint(point) self.lastPointMustStay = True self.firstTimeOnSegment = False # if we are not on a new segemnt, we have to test, if this point is realy needed else: # but only if we have already enough points # TODO: check if this is correct for lines also (they only need two points, to be vaild) if self.rb.numberOfVertices() >= 3: max = self.rb.numberOfVertices() lastRbP = self.rb.getPoint(0, max - 2) # QgsMessageLog.logMessage(str(self.rb.getPoint(0, max-1)), 'rb', 0) nextToLastRbP = self.rb.getPoint(0, max - 3) # QgsMessageLog.logMessage(str(self.rb.getPoint(0, max-2)), 'rb', 0) # QgsMessageLog.logMessage(str(point), 'rb', 0) if not self.pointOnLine( lastRbP, nextToLastRbP, QgsPoint(point)): self.appendPoint(point) self.lastPointMustStay = False else: # TODO: schauen, ob der letzte punkt ein klick war, dann nicht löschen. entsrpechend auch die "punkt auf linie" neu berechenen. if not self.lastPointMustStay: self.rb.removeLastPoint() self.rb.movePoint(point) # QgsMessageLog.logMessage('point removed', 'click', 0) # else: # QgsMessageLog.logMessage('sleep', 'rb', 0) else: self.appendPoint(point) self.lastPointMustStay = False self.firstTimeOnSegment = False else: # if nothing specials happens, just update the rubberband to the cursor position point = QgsMapToPixel.toMapCoordinates( self.canvas.getCoordinateTransform(), x, y) self.rb.movePoint(point) else: # in "not-tracing" state, just update the rubberband to the cursor position # but we have still to snap to act like the "normal" digitize tool (retval, result) = self.snapper.snapToBackgroundLayers(eventPoint) if result != []: point = QgsPoint(result[0].snappedVertex) else: point = QgsMapToPixel.toMapCoordinates( self.canvas.getCoordinateTransform(), x, y) self.rb.movePoint(point) def canvasReleaseEvent(self, event): # with right click the digitizing is finished if event.button() == 2: layer = self.canvas.currentLayer() x = event.pos().x() y = event.pos().y() if layer is not None and self.started: selPoint = QPoint(x, y) (retval, result) = self.snapper.snapToBackgroundLayers(selPoint) if result != []: point = result[0].snappedVertex else: point = QgsMapToPixel.toMapCoordinates( self.canvas.getCoordinateTransform(), x, y) # add this last point self.appendPoint(QgsPoint(point)) self.sendGeometry() def appendPoint(self, point): # don't add the point if it is identical to the last point we added if not (self.lastPoint == point): self.rb.addPoint(point) self.lastPoint = QgsPoint(point) else: pass # see: double QgsGeometryValidator::distLine2Point( QgsPoint p, QgsVector v, QgsPoint q ) # distance of point q from line through p in direction v def pointOnLine(self, pntAft, pntBef, pntTest): p = QgsPoint(pntAft) vx = pntBef.x() - pntAft.x() vy = pntBef.y() - pntAft.y() vlength = math.sqrt(vy * vy + vx * vx) if vlength == 0: return False q = QgsPoint(pntTest) res = (vx * (q.y() - p.y()) - vy * (q.x() - p.x())) / vlength # res = 0 means point is on line, but we are not in a perfect world, so a tolerance is needed # to get rid of some numerical problems. Tolerance estimated by solid engenieering work (rule of thumb...) if res < 1E-10: return True else: return False def sendGeometry(self): layer = self.canvas.currentLayer() coords = [] # backward compatiblity for a bug in qgsRubberband, that was fixed in 1.7 if Qgis.QGIS_VERSION_INT >= 10700: [ coords.append(self.rb.getPoint(0, i)) for i in range(self.rb.numberOfVertices()) ] else: [ coords.append(self.rb.getPoint(0, i)) for i in range(1, self.rb.numberOfVertices()) ] # On the Fly reprojection, not necessary any more, mapToLayerCoordinates is clever enough on its own # layerEPSG = layer.srs().epsg() # projectEPSG = self.canvas.mapRenderer().destinationSrs().epsg() # if layerEPSG != projectEPSG: coords_tmp = coords[:] coords = [] for point in coords_tmp: transformedPoint = self.canvas.mapRenderer().mapToLayerCoordinates( layer, point) coords.append(transformedPoint) coords_tmp = coords[:] coords = [] lastPt = None for pt in coords_tmp: if (lastPt != pt): coords.append(pt) lastPt = pt # Add geometry to feature. if self.isPolygon: g = QgsGeometry().fromPolygon([coords]) else: g = QgsGeometry().fromPolyline(coords) self.traceFound.emit(g) self.rb.reset(self.isPolygon) self.started = False def activate(self): self.canvas.setCursor(self.cursor) def deactivate(self): try: self.rb.reset() except AttributeError: pass def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True
class EditTool(QgsMapTool): wp_clicked = pyqtSignal(int) def __init__(self, mission_track, canvas, msglog): QgsMapTool.__init__(self, canvas) self.setCursor(Qt.CrossCursor) self.mission_track = mission_track self.msglog = msglog self.dragging = False self.feature = None self.vertex = None self.startcoord = None self.layer = self.mission_track.get_mission_layer() logger.info(self.mission_track.get_mission_name()) self.rubber_band = QgsRubberBand(self.canvas(), QgsWkbTypes.LineGeometry) self.rubber_band.setWidth(2) self.rubber_band.setColor(QColor("green")) self.point_cursor_band = QgsRubberBand(self.canvas(), QgsWkbTypes.LineGeometry) self.point_cursor_band.setWidth(1) self.point_cursor_band.setLineStyle(Qt.DashLine) self.point_cursor_band.setColor(QColor(255, 0, 0, 100)) self.mid_point_band = QgsRubberBand(self.canvas(), QgsWkbTypes.PointGeometry) self.mid_point_band.setColor(QColor(255, 0, 0, 100)) self.mid_point_band.setIconSize(18) self.rubber_band_points = QgsRubberBand(self.canvas(), QgsWkbTypes.PointGeometry) self.rubber_band_points.setColor(QColor("green")) self.rubber_band_points.setIcon(QgsRubberBand.ICON_CIRCLE) self.rubber_band_points.setIconSize(10) self.mission_track.mission_changed.connect(self.update_rubber_bands) self.vertex_marker = QgsVertexMarker(self.canvas()) self.start_end_marker = StartEndMarker(canvas, self.mission_track.find_waypoints_in_mission(), QColor("green")) self.layer.startEditing() self.wp = [] self.mCtrl = False # handler for mission feature self.update_rubber_bands(0) crs = canvas.mapSettings().destinationCrs() self.distance_calc = QgsDistanceArea() self.distance_calc.setSourceCrs(crs, QgsProject.instance().transformContext()) self.distance_calc.setEllipsoid(crs.ellipsoidAcronym()) def update_rubber_bands(self, current_wp): self.rubber_band.reset(QgsWkbTypes.LineGeometry) self.rubber_band_points.reset(QgsWkbTypes.PointGeometry) self.wp = self.mission_track.find_waypoints_in_mission() self.start_end_marker.update_markers(self.wp) if len(self.wp) > 0: for v in self.wp: pc = self.toLayerCoordinates(self.layer, QgsPointXY(v)) self.rubber_band.addPoint(pc) self.rubber_band_points.addPoint(pc) logger.debug("MISSION UPDATE: now we have {} waypoints".format(len(self.wp))) self.vertex_marker.setCenter(QgsPointXY(self.wp[current_wp].x(), self.wp[current_wp].y())) self.vertex_marker.setColor(QColor(25, 255, 0)) self.vertex_marker.setIconSize(7) self.vertex_marker.setIconType(QgsVertexMarker.ICON_X) # ICON_BOX, ICON_CROSS, ICON_X self.vertex_marker.setPenWidth(2) self.vertex_marker.show() self.set_geometry() else: self.vertex_marker.hide() def set_control_state(self, state): self.mCtrl = state def keyPressEvent(self, event): if event.key() == Qt.Key_Control and not self.dragging: self.mCtrl = True pos = self.canvas().mouseLastXY() if not self.find_on_feature(pos, self.calc_tolerance()): self.show_dist_and_bearing_to_point() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = False pos = self.canvas().mouseLastXY() if not self.find_on_feature(pos, self.calc_tolerance()) and not self.dragging: self.show_dist_and_bearing_to_point() return def canvasDoubleClickEvent(self, event): self.canvasPressEvent(event) def canvasPressEvent(self, event): if self.dragging: self.canvasReleaseEvent(event) map_pt, layer_pt = self.transform_coordinates(event.pos()) tolerance = self.calc_tolerance() if not self.find_on_feature(event.pos(), tolerance): if event.button() == Qt.LeftButton: # we have clicked outside the track logger.debug("We have clicked outside the track") self.point_cursor_band.reset(QgsWkbTypes.LineGeometry) if not self.mCtrl: # add step to mission at the end self.mission_track.add_step(len(self.wp), layer_pt) self.show_waypoint_distances(len(self.wp)-1) else: self.mission_track.add_step(0, layer_pt) self.show_waypoint_distances(0) else: logger.debug("We have clicked on the track") vertex = self.find_vertex_at(event.pos(), tolerance) if event.button() == Qt.LeftButton: if vertex is None: logger.debug("We have clicked between vertexs") # we have clicked in between vertex, add intermediate point initial_vertex = self.find_segment_at(event.pos()) # self.mission_track.add_step(initial_vertex + 1, layerPt) intersection = intersect_point_to_line(self.toLayerCoordinates(self.layer, event.pos()), QgsPointXY(self.wp[initial_vertex]), QgsPointXY(self.wp[initial_vertex + 1])) logger.debug("intersection point: {} {}".format(str(intersection.x()), str(intersection.y()))) logger.debug("{} {} {} {}".format(self.wp[initial_vertex].x(), self.wp[initial_vertex].y(), self.wp[initial_vertex + 1].x(), self.wp[initial_vertex + 1].y())) # layerPtIntersection = self.toLayerCoordinates(self.layer,intersection) self.mission_track.add_step(initial_vertex + 1, intersection) self.mid_point_band.reset(QgsWkbTypes.PointGeometry) self.show_waypoint_distances(initial_vertex+1) else: logger.debug("We have clicked on vertex {}".format(vertex)) # we have clicked on a vertex # Left click -> move vertex. self.dragging = True self.vertex = vertex self.startcoord = event.pos() # self.moveVertexTo(layerPt) elif event.button() == Qt.RightButton: if vertex is not None and not self.dragging: # Right click -> delete vertex. self.delete_vertex(vertex) if self.find_on_feature(event.pos(), tolerance): # If cursor still over track vertex = self.find_vertex_at(event.pos(), tolerance) if vertex is None: # Cursor is between vertexes self.show_mid_point(event.pos()) else: # Cursor is over a vertex self.show_waypoint_distances(vertex) else: self.show_dist_and_bearing_to_point() def transform_coordinates(self, canvas_pt): return (self.toMapCoordinates(canvas_pt), self.toLayerCoordinates(self.layer, canvas_pt)) def canvasMoveEvent(self, event): if self.dragging: self.move_vertex_to(self.toLayerCoordinates(self.layer, event.pos())) self.mission_track.hide_start_end_markers() self.vertex_marker.hide() self.start_end_marker.hide_markers() self.show_waypoint_distances(self.vertex) else: tolerance = self.calc_tolerance() if self.find_on_feature(event.pos(), tolerance): # if mouse is over the track self.point_cursor_band.reset(QgsWkbTypes.LineGeometry) self.mid_point_band.reset(QgsWkbTypes.PointGeometry) vertex = self.find_vertex_at(event.pos(), tolerance) if vertex is None: # Cursor is between vertexes self.show_mid_point(event.pos()) else: # Cursor is over a vertex self.show_waypoint_distances(vertex) else: self.mid_point_band.reset(QgsWkbTypes.PointGeometry) self.show_dist_and_bearing_to_point() def show_dist_and_bearing_to_point(self): """ Finds distance and bearing from the last point (first if pressing ctrl) to the specified point and shows them in the message log. Also draws a line between the points. """ bearing = 0.0 self.point_cursor_band.reset(QgsWkbTypes.LineGeometry) point = self.canvas().mouseLastXY() if len(self.wp) > 0: cursor_point = self.toMapCoordinates(point) if self.mCtrl: anchor_point = QgsPointXY(self.wp[0]) else: anchor_point = QgsPointXY(self.wp[len(self.wp) - 1]) self.point_cursor_band.addPoint(cursor_point) self.point_cursor_band.addPoint(anchor_point) distance = self.distance_calc.measureLine([anchor_point, cursor_point]) if distance != 0.0: bearing = self.distance_calc.bearing(anchor_point, cursor_point) self.msglog.logMessage("") if self.mCtrl: msg = "Distance to next point: " else: msg = "Distance to previous point: " self.msglog.logMessage(msg + "{:.3F} m. Bearing: {:.3F} º.".format(distance, math.degrees(bearing)), "Distance and bearing", 0) else: self.msglog.logMessage("") def show_mid_point(self, cursor): """ Finds the projection of the cursor over the track and draws a circle in that point. Finds the distances between this projection point and the previous and next points in the mission and shows them in the message log. :param cursor: position to be projected over the track """ prev_vertex = self.find_segment_at(cursor) prev_point = QgsPointXY(self.wp[prev_vertex]) next_point = QgsPointXY(self.wp[prev_vertex + 1]) cursor_point = self.toMapCoordinates(cursor) intersection = intersect_point_to_line(cursor_point, prev_point, next_point) self.mid_point_band.addPoint(intersection) distance1 = self.distance_calc.measureLine([prev_point, intersection]) distance2 = self.distance_calc.measureLine([intersection, next_point]) self.msglog.logMessage("") self.msglog.logMessage("Distance to previous point: {:.3F} m. Distance to next point: {:.3F} m." .format(distance1, distance2), "Distance between points", 0) def show_waypoint_distances(self, vertex): """ Finds the distances to adjacent waypoints of vertex and shows them in the message log :param vertex: index of the waypoint from the mission """ curr_point = self.rubber_band_points.getPoint(QgsWkbTypes.PointGeometry, vertex) if vertex == 0: if len(self.wp) > 1: next_point = QgsPointXY(self.wp[vertex+1]) distance = self.distance_calc.measureLine([curr_point, next_point]) bearing = self.distance_calc.bearing(next_point, curr_point) msg = "Distance to next point: {:.3F} m. Bearing: {:.3F} º.".format(distance, math.degrees(bearing)) else: msg = "" self.msglog.logMessage("") self.msglog.logMessage(msg+" (Waypoint {}) ".format(vertex+1), "Vertex distances", 0) elif vertex == len(self.wp) - 1: prev_point = QgsPointXY(self.wp[vertex-1]) distance = self.distance_calc.measureLine([prev_point, curr_point]) bearing = self.distance_calc.bearing(prev_point, curr_point) msg = "Distance to previous point: {:.3F} m. Bearing: {:.3F} º.".format(distance, math.degrees(bearing)) self.msglog.logMessage("") self.msglog.logMessage(msg+" (Waypoint {})".format(vertex+1), "Vertex distances", 0) else: prev_point = QgsPointXY(self.wp[vertex-1]) next_point = QgsPointXY(self.wp[vertex+1]) distance1 = self.distance_calc.measureLine(prev_point, curr_point) distance2 = self.distance_calc.measureLine(curr_point, next_point) msg = "Distance to previous point: {:.3F} m. Distance to next point: {:.3F} m."\ .format(distance1, distance2) self.msglog.logMessage("") self.msglog.logMessage(msg+" (Waypoint {})".format(vertex+1), "Vertex distances", 0) def hide_point_cursor_band(self): """ Hides the rubber band drawn from last (or first) point to cursor """ self.point_cursor_band.reset(QgsWkbTypes.LineGeometry) def canvasReleaseEvent(self, event): if self.dragging and event.button() == Qt.LeftButton: self.dragging = False self.vertex_marker.show() mapPt, layerPt = self.transform_coordinates(event.pos()) # Check distance with initial point dist = math.sqrt( (self.startcoord.x() - event.pos().x()) ** 2 + (self.startcoord.y() - event.pos().y()) ** 2) tolerance = self.calc_tolerance() if dist > tolerance: self.move_vertex_to(layerPt) self.mission_track.change_position(self.vertex, layerPt) self.wp_clicked.emit(self.vertex) self.feature = None self.vertex = None self.layer.updateExtents() else: # If release point is the same, has been just a click self.move_vertex_to(self.toLayerCoordinates(self.layer, QgsPointXY(self.wp[self.vertex]))) self.wp_clicked.emit(self.vertex) self.feature = None self.vertex = None def calc_tolerance(self): """ Compute the tolerance on canvas :return: tolerance """ # 2% of tolerance width_tolerance = 0.02 * self.canvas().width() height_tolerance = 0.02 * self.canvas().height() if width_tolerance < height_tolerance: tolerance = width_tolerance else: tolerance = height_tolerance return tolerance def move_vertex_to(self, layerPt): """ Move current vertex to layerPt position. :param layerPt: layer point :type: QgsPointXY """ if len(self.wp) > 1: self.rubber_band.movePoint(self.vertex, layerPt) self.rubber_band_points.movePoint(self.vertex, layerPt) elif len(self.wp) == 1: # A rubber band with PointGeometry and only 1 point acts as if it had 2 points, we need to reset it in # order to move the point. self.rubber_band_points.reset(QgsWkbTypes.PointGeometry) self.rubber_band_points.addPoint(layerPt) def delete_vertex(self, vertex): """ Delete step 'vertex'. :param vertex: step """ self.mission_track.remove_step(vertex) self.dragging = False self.vertex = None def find_on_feature(self, pos, tolerance): """ if clicked point has some segment at a smaller distance than tolerance means that we've clicked on the track :param pos: The point that we've clicked :param tolerance: The tolerance of pos :return: bool """ if len(self.wp) > 1: dist_to_segment = [] for v in range(0, len(self.wp) - 1): # convert layer coordinates to canvas coordinates a1 = self.toCanvasCoordinates(QgsPointXY(self.wp[v])) b1 = self.toCanvasCoordinates(QgsPointXY(self.wp[v + 1])) dist_to_segment.append( self.dist_to_segment(a1.x(), a1.y(), b1.x(), b1.y(), pos.x(), pos.y())) logger.debug("dist to segment: {}".format(dist_to_segment)) if dist_to_segment[v] < tolerance: return True return False else: # last waypoint vertex = self.find_vertex_at(pos, tolerance) if vertex is None: return False else: return True def find_segment_at(self, pos): """ get the segment that is closer to the clicked point and return its initial vertex :param pos: the point that we've clicked :return: initial vertex of the segment """ dist_to_segment = [] for v in range(0, len(self.wp) - 1): a1 = self.toCanvasCoordinates(QgsPointXY(self.wp[v])) b1 = self.toCanvasCoordinates(QgsPointXY(self.wp[v + 1])) dist_to_segment.append(self.dist_to_segment(a1.x(), a1.y(), b1.x(), b1.y(), pos.x(), pos.y())) vertex = dist_to_segment.index(min(dist_to_segment)) return vertex def find_vertex_at(self, pos, tolerance): """ get the vertex that is closer to the clicked point :param pos: The point that we've clicked :param tolerance: The tolerance of pos :return: vertex or None """ if len(self.wp) > 0: dist_to_vertex = [] logger.debug("tolerance {}".format(tolerance)) for v in range(0, len(self.wp)): a1 = self.toCanvasCoordinates(QgsPointXY(self.wp[v])) dist_to_vertex.append(math.sqrt((pos.x() - a1.x()) ** 2 + (pos.y() - a1.y()) ** 2)) logger.debug("dist to vertex: {}".format(dist_to_vertex)) vertex = dist_to_vertex.index(min(dist_to_vertex)) if min(dist_to_vertex) > tolerance: return None else: logger.debug("ON VERTEX") return vertex else: return None def dist_to_segment(self, ax, ay, bx, by, cx, cy): """ Computes the minimum distance between a point (cx, cy) and a line segment with endpoints (ax, ay) and (bx, by). :param ax: endpoint 1, x-coordinate :param ay: endpoint 1, y-coordinate :param bx: endpoint 2, x-coordinate :param by: endpoint 2, y-coordinate :param cx: point, x-coordinate :param cy: point, x-coordinate :return: minimum distance between point and line segment """ # calculate tolerance tolerance = self.calc_tolerance() # get distance between points c-a and c-b dist_to_a = math.sqrt((cx - ax) ** 2 + (cy - ay) ** 2) dist_to_b = math.sqrt((cx - bx) ** 2 + (cy - by) ** 2) # if distance to point a or distance to point b is smaller than tolerance, return -1 if (dist_to_a < tolerance) or (dist_to_b < tolerance): return -1 # if one coordinate are between a coordinates or b coordinates if is_between(ax, ay, bx, by, cx, cy): y = (by - ay) x = (bx - ax) # line defined by two points formula num = abs((y * cx) - (x * cy) + (bx * ay) - (by * ax)) den = math.sqrt(y ** 2 + x ** 2) dl = num / den return dl else: return tolerance + 1 def set_geometry(self): """ Save rubber band to geometry of the layer """ if self.layer.featureCount() == 0: # no feature yet created f = QgsFeature() if len(self.wp) == 1: f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(self.wp[0].x(), self.wp[0].y()))) else: f.setGeometry(QgsGeometry.fromPolyline(self.wp)) # self.layer.dataProvider().addFeatures([f]) self.layer.addFeatures([f]) else: # mission feature present, edit geometry feats = self.layer.getFeatures() for f in feats: if len(self.wp) == 1: self.layer.changeGeometry(f.id(), QgsGeometry.fromPointXY(QgsPointXY(self.wp[0].x(), self.wp[0].y()))) else: self.layer.changeGeometry(f.id(), QgsGeometry.fromPolyline(self.wp)) self.layer.commitChanges() self.layer.startEditing() def close_band(self): self.start_end_marker.close_markers() self.vertex_marker.hide() self.canvas().scene().removeItem(self.vertex_marker) self.vertex_marker = None self.mission_track.mission_changed.disconnect() self.layer.commitChanges() self.rubber_band.hide() self.mid_point_band.hide() self.rubber_band_points.hide() self.point_cursor_band.hide() self.canvas().scene().removeItem(self.rubber_band) self.canvas().scene().removeItem(self.mid_point_band) self.canvas().scene().removeItem(self.rubber_band_points) self.canvas().scene().removeItem(self.point_cursor_band) self.rubber_band = None self.mid_point_band = None self.rubber_band_points = None self.point_cursor_band = None self.msglog.logMessage("")
class APISMapToolEmitPolygonAndPoint(QgsMapTool, APISMapToolMixin): mappingFinished = pyqtSignal(QgsGeometry, QgsGeometry, QgsCoordinateReferenceSystem) def __init__(self, canvas): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.rubberBand = None self.tempRubberBand = None self.vertexMarker = None self.capturedPoints = [] self.derivedPoint = None self.capturing = False self.setCursor(Qt.CrossCursor) def canvasReleaseEvent(self, event): if event.button() == Qt.LeftButton: if not self.capturing: self.startCapturing() self.addVertex(event.pos()) elif event.button() == Qt.RightButton: point = self.getDerivedPoint() polygon = self.getCapturedPolygon() self.stopCapturing() if point is not None and polygon is not None: pointGeom = self.getPointGeometry(point) polygonGeom = self.getPolygonGeometry(polygon) if pointGeom is not None and polygonGeom is not None: self.mappingFinished.emit( pointGeom, polygonGeom, self.canvas.mapSettings().destinationCrs()) else: self.clearScene() else: self.clearScene() def canvasMoveEvent(self, event): if self.tempRubberBand is not None and self.capturing: mapPt = self.transformCoordinates(event.pos()) self.tempRubberBand.movePoint(mapPt) def keyPressEvent(self, event): if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete: self.removeLastVertex() event.ignore() if event.key() == Qt.Key_Escape: self.stopCapturing() self.clearScene() if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: point = self.getDerivedPoint() polygon = self.getCapturedPolygon() self.stopCapturing() if point is not None and polygon is not None: pointGeom = self.getPointGeometry(point) polygonGeom = self.getPolygonGeometry(polygon) if pointGeom is not None and polygonGeom is not None: self.mappingFinished.emit( pointGeom, polygonGeom, self.canvas.mapSettings().destinationCrs()) else: self.clearScene() else: self.clearScene() def startCapturing(self): self.clearScene() self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubberBand.setWidth(2) self.rubberBand.setFillColor(QColor(220, 0, 0, 120)) self.rubberBand.setStrokeColor(QColor(220, 0, 0)) self.rubberBand.setLineStyle(Qt.DotLine) self.rubberBand.show() self.tempRubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.tempRubberBand.setWidth(2) self.tempRubberBand.setFillColor(QColor(0, 0, 0, 0)) self.tempRubberBand.setStrokeColor(QColor(220, 0, 0)) self.tempRubberBand.setLineStyle(Qt.DotLine) self.tempRubberBand.show() self.vertexMarker = QgsVertexMarker(self.canvas) self.vertexMarker.setIconType(1) self.vertexMarker.setColor(QColor(220, 0, 0)) self.vertexMarker.setIconSize(16) self.vertexMarker.setPenWidth(3) self.vertexMarker.show() self.capturing = True def clearScene(self): if self.vertexMarker: self.canvas.scene().removeItem(self.vertexMarker) self.vertexMarker = None if self.rubberBand: self.canvas.scene().removeItem(self.rubberBand) self.rubberBand = None if self.tempRubberBand: self.canvas.scene().removeItem(self.tempRubberBand) self.tempRubberBand = None def stopCapturing(self): if self.vertexMarker and self.rubberBand and self.rubberBand.numberOfVertices( ) < 3: self.canvas.scene().removeItem(self.vertexMarker) self.vertexMarker = None if self.rubberBand and self.rubberBand.numberOfVertices() < 3: self.canvas.scene().removeItem(self.rubberBand) self.rubberBand = None if self.tempRubberBand: self.canvas.scene().removeItem(self.tempRubberBand) self.tempRubberBand = None self.capturing = False self.capturedPoints = [] self.derivedPoint = None self.canvas.refresh() def addVertex(self, canvasPoint): mapPt = self.transformCoordinates(canvasPoint) self.rubberBand.addPoint(mapPt) self.capturedPoints.append(mapPt) bandSize = self.rubberBand.numberOfVertices() if bandSize > 2: rubGeom = self.rubberBand.asGeometry() cpGeom = rubGeom.centroid() if not rubGeom.contains(cpGeom): cpGeom = rubGeom.pointOnSurface() #nearestCp = rubGeom.nearestPoint(cpGeom) self.vertexMarker.setCenter(cpGeom.asPoint()) self.derivedPoint = cpGeom.asPoint() self.vertexMarker.show() self.tempRubberBand.reset(QgsWkbTypes.PolygonGeometry) firstPoint = self.rubberBand.getPoint(0, 0) self.tempRubberBand.addPoint(firstPoint) self.tempRubberBand.movePoint(mapPt) self.tempRubberBand.addPoint(mapPt) def removeLastVertex(self): if not self.capturing: return bandSize = self.rubberBand.numberOfVertices() tempBandSize = self.tempRubberBand.numberOfVertices() numPoints = len(self.capturedPoints) if bandSize < 1 or numPoints < 1: return self.rubberBand.removePoint(-1) if bandSize > 1: if tempBandSize > 1: point = self.rubberBand.getPoint(0, bandSize - 2) self.tempRubberBand.movePoint(tempBandSize - 2, point) else: self.tempRubberBand.reset(QgsWkbTypes.PolygonGeometry) bandSize = self.rubberBand.numberOfVertices() if bandSize < 3: self.vertexMarker.hide() else: rubGeom = self.rubberBand.asGeometry() cpGeom = rubGeom.centroid() if not rubGeom.contains(cpGeom): cpGeom = rubGeom.pointOnSurface() #nearestCp = rubGeom.nearestPoint(cpGeom) self.vertexMarker.setCenter(cpGeom.asPoint()) self.derivedPoint = cpGeom.asPoint() self.vertexMarker.show() del self.capturedPoints[-1] def getCapturedPolygon(self): polygon = self.capturedPoints if len(polygon) < 3: return None else: return polygon def getDerivedPoint(self): point = self.derivedPoint if point is None: return None else: return point def getPointGeometry(self, geom): p = QgsGeometry.fromPointXY(geom) if p.isGeosValid() and not p.isEmpty(): return p else: return None def getPolygonGeometry(self, geom): p = QgsGeometry.fromPolygonXY([geom]) if p.isGeosValid() and not p.isEmpty(): return p else: return None
class SignatureTool(QgsMapTool): """ On Double click it will callback with an array with a single pixel. On Single click without releasing it will draw a square and callback the starting and ending point in an array. On Single click with releasing it will start drawing a polygon and every subsequent single click will add a new vertex in the polygon. On Right click it will callback with an array with all the vertex in the polygon. On Escape it will clean up the array and start over. """ def __init__(self, canvas, layer, callback): QgsMapTool.__init__(self, canvas) self._canvas = canvas self._layer = layer self._callback = callback self._pixels = [] self._start_point = None self._mode = Mode.NONE self._rubber_band = QgsRubberBand(self._canvas) self._rubber_band.setColor(Qt.red) self._rubber_band.setWidth(1) self.parent().setCursor(Qt.CrossCursor) def getPoint(self, pos): x = pos.x() y = pos.y() return self._canvas.getCoordinateTransform().toMapCoordinates(x, y) def getRowCol(self, point): # clicked position on screen to map coordinates data_provider = self._layer.dataProvider() extent = data_provider.extent() width = data_provider.xSize() if data_provider.capabilities() \ & data_provider.Size else 1000 height = data_provider.ySize() if data_provider.capabilities() \ & data_provider.Size else 1000 xres = extent.width() / width yres = extent.height() / height if extent.xMinimum() <= point.x() <= extent.xMaximum() and \ extent.yMinimum() <= point.y() <= extent.yMaximum(): col = int(floor((point.x() - extent.xMinimum()) / xres)) row = int(floor((extent.yMaximum() - point.y()) / yres)) return (row, col) else: return None def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self._pixels = [] self.finish() def canvasMoveEvent(self, event): point = self.getPoint(event.pos()) if self._mode is Mode.SQUARE: if not point.compare(self._start_point): self._rubber_band.reset() self._rubber_band.addPoint(self._start_point, False) self._rubber_band.addPoint( QgsPointXY(self._start_point.x(), point.y()), False) self._rubber_band.addPoint(point, False) self._rubber_band.addPoint( QgsPointXY(point.x(), self._start_point.y()), False) self._rubber_band.closePoints() elif self._mode is Mode.POLYGON: self._rubber_band.movePoint( self._rubber_band.numberOfVertices() - 1, point) def canvasReleaseEvent(self, event): if event.button() == Qt.LeftButton: point = self.getPoint(event.pos()) if self._mode is Mode.SQUARE: if self._start_point.compare(point): self._mode = Mode.POLYGON self._rubber_band.addPoint(point) self._start_point = None else: self._pixels = [] # The last vertex is repeated for i in range(self._rubber_band.numberOfVertices() - 1): self._pixels.append( self.getRowCol(self._rubber_band.getPoint(0, i))) self.finish() elif self._mode is Mode.POLYGON: self._rubber_band.addPoint(point) def canvasPressEvent(self, event): if event.button() == Qt.LeftButton: point = self.getPoint(event.pos()) pixel = self.getRowCol(point) if self._mode is Mode.NONE: self._mode = Mode.SQUARE self._start_point = QgsPointXY(point.x(), point.y()) elif self._mode is Mode.POLYGON: self._rubber_band.removePoint( self._rubber_band.numberOfVertices() - 1) else: self._mode = Mode.POLYGON if pixel: self._rubber_band.addPoint(point) self._pixels.append(pixel) elif event.button() == Qt.RightButton: self.finish() def finish(self): self._canvas.unsetMapTool(self) self._rubber_band.reset() self._mode = Mode.NONE self._start_point = None if len(self._pixels) > 0: self._callback(self._layer.hiperqube_id(), self._pixels)
class LineMapTool(QgsMapTool): def __init__(self, iface, settings, action, index_action): ''' Class constructor ''' self.iface = iface self.canvas = self.iface.mapCanvas() self.settings = settings self.index_action = index_action self.elem_type_type = self.settings.value('insert_values/'+str(index_action)+'_elem_type_type') QgsMapTool.__init__(self, self.canvas) self.setAction(action) # Set rubber band features self.rubberBand = QgsRubberBand(self.canvas, QGis.Line) mFillColor = QColor(255, 0, 0); self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(2) self.reset() # Vertex marker self.vertexMarker = QgsVertexMarker(self.canvas) self.vertexMarker.setColor(QColor(0, 255, 0)) self.vertexMarker.setIconSize(9) self.vertexMarker.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.vertexMarker.setPenWidth(5) # Snapper self.snapper = QgsMapCanvasSnapper(self.canvas) # Control button state self.mCtrl = False self.started = False # Tracing options self.firstTimeOnSegment = True self.lastPointMustStay = False self.lastPoint = None # Change map tool cursor self.cursor = QCursor() self.cursor.setShape(Qt.CrossCursor) self.parent().setCursor(self.cursor) def keyPressEvent(self, event): ''' We need to know, if ctrl-key is pressed ''' if event.key() == Qt.Key_Control: self.mCtrl = True def keyReleaseEvent(self, event): ''' Ctrl-key is released ''' if event.key() == Qt.Key_Control: self.mCtrl = False # Remove the last added point when the delete key is pressed if event.key() == Qt.Key_Backspace: self.rubberBand.removeLastPoint() def reset(self): self.start_point = self.end_point = None self.isEmittingPoint = False self.rubberBand.reset(QGis.Line) ''' QgsMapTools inherited event functions ''' def canvasPressEvent(self, event): ''' On left click, we add a point ''' if event.button() == 1: # layer = self.canvas.currentLayer() layer = self.iface.activeLayer() # Declare, that are we going to work self.started = True if layer <> None: x = event.pos().x() y = event.pos().y() selPoint = QPoint(x,y) # Check something snapped (retval,result) = self.snapper.snapToBackgroundLayers(selPoint) #@UnusedVariable # The point we want to have, is either from snapping result if result <> []: point = result[0].snappedVertex # If we snapped something, it's either a vertex if result[0].snappedVertexNr <> -1: self.firstTimeOnSegment = True # Or a point on a segment, so we have to declare, that a point on segment is found else: self.firstTimeOnSegment = False # Or its some point from out in the wild else: point = QgsMapToPixel.toMapCoordinates(self.canvas.getCoordinateTransform(), x, y) self.firstTimeOnSegment = True # Bring the rubberband to the cursor i.e. the clicked point self.rubberBand.movePoint(point) # Set a new point to go on with self.appendPoint(point) # Try to remember that this point was on purpose i.e. clicked by the user self.lastPointMustStay = True self.firstTimeOnSegment = True def canvasMoveEvent(self, event): # Hide highlight self.vertexMarker.hide() # Get the click x = event.pos().x() y = event.pos().y() eventPoint = QPoint(x,y) # Snapping (retval,result) = self.snapper.snapToBackgroundLayers(eventPoint) #@UnusedVariable # That's the snapped point if result <> []: point = QgsPoint(result[0].snappedVertex) # Add marker self.vertexMarker.setCenter(point) self.vertexMarker.show() # Check tracing if self.started: # Only if the ctrl key is pressed if self.mCtrl == True: # So if we have found a snapping if result <> []: # If it is a vertex, not a point on a segment if result[0].snappedVertexNr <> -1: self.rubberBand.movePoint(point) self.appendPoint(point) self.lastPointMustStay = True # The next point found, may be on a segment self.firstTimeOnSegment = True # We are on a segment else: self.rubberBand.movePoint(point) # If we are on a new segment, we add the point in any case if self.firstTimeOnSegment: self.appendPoint(point) self.lastPointMustStay = True self.firstTimeOnSegment = False # if we are not on a new segment, we have to test, if this point is really needed else: # but only if we have already enough points if self.rubberBand.numberOfVertices() >=3: num_vertexs = self.rubberBand.numberOfVertices() lastRbP = self.rubberBand.getPoint(0, num_vertexs-2) nextToLastRbP = self.rubberBand.getPoint(0, num_vertexs-3) if not self.pointOnLine(lastRbP, nextToLastRbP, QgsPoint(point)): self.appendPoint(point) self.lastPointMustStay = False else: if not self.lastPointMustStay: self.rubberBand.removeLastPoint() self.rubberBand.movePoint(point) else: self.appendPoint(point) self.lastPointMustStay = False self.firstTimeOnSegment = False else: #if nothing specials happens, just update the rubberband to the cursor position point = QgsMapToPixel.toMapCoordinates(self.canvas.getCoordinateTransform (), x, y) self.rubberBand.movePoint(point) else: ''' In "not-tracing" state, just update the rubberband to the cursor position but we have still to snap to act like the "normal" digitize tool ''' if result <> []: point = QgsPoint(result[0].snappedVertex) # Add marker self.vertexMarker.setCenter(point) self.vertexMarker.show() else: point = QgsMapToPixel.toMapCoordinates(self.canvas.getCoordinateTransform(), x, y) self.rubberBand.movePoint(point) def canvasReleaseEvent(self, event): ''' With right click the digitizing is finished ''' if event.button() == 2: # layer = self.canvas.currentLayer() layer = self.iface.activeLayer() x = event.pos().x() y = event.pos().y() if layer <> None and self.started == True: selPoint = QPoint(x,y) (retval,result) = self.snapper.snapToBackgroundLayers(selPoint) #@UnusedVariable if result <> []: point = result[0].snappedVertex #@UnusedVariable else: point = QgsMapToPixel.toMapCoordinates(self.canvas.getCoordinateTransform(), x, y) #@UnusedVariable self.sendGeometry() def appendPoint(self, point): ''' Don't add the point if it is identical to the last point we added ''' if not (self.lastPoint == point) : self.rubberBand.addPoint(point) self.lastPoint = QgsPoint(point) else: pass def sendGeometry(self): #layer = self.canvas.currentLayer() layer = self.iface.activeLayer() coords = [] self.rubberBand.removeLastPoint() if QGis.QGIS_VERSION_INT >= 10700: [coords.append(self.rubberBand.getPoint(0, i)) for i in range(self.rubberBand.numberOfVertices())] else: [coords.append(self.rubberBand.getPoint(0,i)) for i in range(1,self.rubberBand.numberOfVertices())] # On the Fly reprojection, not necessary any more, mapToLayerCoordinates is clever enough on its own #layerEPSG = layer.srs().epsg() #projectEPSG = self.canvas.mapRenderer().destinationSrs().epsg() #if layerEPSG != projectEPSG: coords_tmp = coords[:] coords = [] for point in coords_tmp: transformedPoint = self.canvas.mapRenderer().mapToLayerCoordinates( layer, point ); coords.append(transformedPoint) # Filter duplicated points coords_tmp = coords[:] coords = [] lastPt = None for pt in coords_tmp: if (lastPt <> pt) : coords.append(pt) lastPt = pt # Add geometry to feature. g = QgsGeometry().fromPolyline(coords) self.rubberBand.reset(QGis.Line) self.started = False # Write the feature self.createFeature(g) def createFeature(self, geom): # layer = self.canvas.currentLayer() layer = self.iface.activeLayer() provider = layer.dataProvider() f = QgsFeature() if (geom.isGeosValid()): f.setGeometry(geom) else: reply = QMessageBox.question(self.iface.mainWindow(), 'Feature not valid', "The geometry of the feature you just added isn't valid. Do you want to use it anyway?", QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: f.setGeometry(geom) else: return False # Add attribute fields to feature. fields = layer.pendingFields() try: #API-Break 1.8 vs. 2.0 handling attr = f.initAttributes(len(fields)) #@UnusedVariable for i in range(len(fields)): f.setAttribute(i, provider.defaultValue(i)) except AttributeError: #<=1.8 # Add attributefields to feature. for i in fields: f.addAttribute(i, provider.defaultValue(i)) idx = layer.fieldNameIndex('epa_type') f[idx] = self.elem_type_type # Upload changes layer.startEditing() layer.addFeature(f) # Control PostgreSQL exceptions boolOk = layer.commitChanges() # Update canvas self.canvas.refresh() # Capture edit exception if boolOk: # Spatial query to retrieve last added line, start searchingcandidates cands = layer.getFeatures(QgsFeatureRequest().setFilterRect(f.geometry().boundingBox())) # Iterate on candidates for line_feature in cands: if line_feature.geometry().equals(f.geometry()): # Highlight layer.setSelectedFeatures([line_feature.id()]) # Open form self.iface.openFeatureForm(layer, line_feature) break else: # Delete layer.rollBack() # User error msg = "Error adding PIPE: Typically this occurs if\n the first point is not located in a existing\n node or feature is out of the defined sectors." QMessageBox.information(None, "PostgreSQL error:", msg) def activate(self): self.canvas.setCursor(self.cursor) def deactivate(self): try: self.rubberBand.reset(QGis.Line) except AttributeError: pass
class ApisMapToolEmitPolygonAndPoint(QgsMapTool, ApisMapToolMixin): mappingFinished = pyqtSignal(QgsGeometry, QgsGeometry, QgsCoordinateReferenceSystem) def __init__(self, canvas): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.rubberBand = None self.tempRubberBand = None self.vertexMarker = None self.capturedPoints = [] self.derivedPoint = None self.capturing = False self.setCursor(Qt.CrossCursor) def canvasReleaseEvent(self, event): if event.button() == Qt.LeftButton: if not self.capturing: self.startCapturing() self.addVertex(event.pos()) elif event.button() == Qt.RightButton: point = self.getDerivedPoint() polygon = self.getCapturedPolygon() self.stopCapturing() if point != None and polygon != None: pointGeom = self.getPointGeometry(point) polygonGeom = self.getPolygonGeometry(polygon) if pointGeom != None and polygonGeom != None: self.mappingFinished.emit(pointGeom, polygonGeom, self.canvas.mapSettings().destinationCrs()) else: self.clearScene() else: self.clearScene() def canvasMoveEvent(self, event): if self.tempRubberBand != None and self.capturing: mapPt = self.transformCoordinates(event.pos()) self.tempRubberBand.movePoint(mapPt) def keyPressEvent(self, event): if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete: self.removeLastVertex() event.ignore() if event.key() == Qt.Key_Escape: self.stopCapturing() self.clearScene() if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: point = self.getDerivedPoint() polygon = self.getCapturedPolygon() self.stopCapturing() if point != None and polygon != None: pointGeom = self.getPointGeometry(point) polygonGeom = self.getPolygonGeometry(polygon) if pointGeom != None and polygonGeom != None: self.mappingFinished.emit(pointGeom, polygonGeom, self.canvas.mapSettings().destinationCrs()) else: self.clearScene() else: self.clearScene() def startCapturing(self): self.clearScene() self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.rubberBand.setWidth(2) self.rubberBand.setFillColor(QColor(220, 0, 0, 120)) self.rubberBand.setBorderColor(QColor(220, 0, 0)) self.rubberBand.setLineStyle(Qt.DotLine) self.rubberBand.show() self.tempRubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.tempRubberBand.setWidth(2) self.tempRubberBand.setFillColor(QColor(0, 0, 0, 0)) self.tempRubberBand.setBorderColor(QColor(220, 0, 0)) self.tempRubberBand.setLineStyle(Qt.DotLine) self.tempRubberBand.show() self.vertexMarker = QgsVertexMarker(self.canvas) self.vertexMarker.setIconType(1) self.vertexMarker.setColor(QColor(220, 0, 0)) self.vertexMarker.setIconSize(16) self.vertexMarker.setPenWidth(3) self.vertexMarker.show() self.capturing = True def clearScene(self): if self.vertexMarker: self.canvas.scene().removeItem(self.vertexMarker) self.vertexMarker = None if self.rubberBand: self.canvas.scene().removeItem(self.rubberBand) self.rubberBand = None if self.tempRubberBand: self.canvas.scene().removeItem(self.tempRubberBand) self.tempRubberBand = None def stopCapturing(self): if self.vertexMarker and self.rubberBand and self.rubberBand.numberOfVertices() < 3: self.canvas.scene().removeItem(self.vertexMarker) self.vertexMarker = None if self.rubberBand and self.rubberBand.numberOfVertices() < 3: self.canvas.scene().removeItem(self.rubberBand) self.rubberBand = None if self.tempRubberBand: self.canvas.scene().removeItem(self.tempRubberBand) self.tempRubberBand = None self.capturing = False self.capturedPoints = [] self.derivedPoint = None self.canvas.refresh() def addVertex(self, canvasPoint): mapPt = self.transformCoordinates(canvasPoint) self.rubberBand.addPoint(mapPt) self.capturedPoints.append(mapPt) bandSize = self.rubberBand.numberOfVertices() if bandSize > 2: rubGeom = self.rubberBand.asGeometry() cpGeom = rubGeom.centroid() if not rubGeom.contains(cpGeom): cpGeom = rubGeom.pointOnSurface() #nearestCp = rubGeom.nearestPoint(cpGeom) self.vertexMarker.setCenter(cpGeom.asPoint()) self.derivedPoint = cpGeom.asPoint() self.vertexMarker.show() self.tempRubberBand.reset(QGis.Polygon) firstPoint = self.rubberBand.getPoint(0, 0) self.tempRubberBand.addPoint(firstPoint) self.tempRubberBand.movePoint(mapPt) self.tempRubberBand.addPoint(mapPt) def removeLastVertex(self): if not self.capturing: return bandSize = self.rubberBand.numberOfVertices() tempBandSize = self.tempRubberBand.numberOfVertices() numPoints = len(self.capturedPoints) if bandSize < 1 or numPoints < 1: return self.rubberBand.removePoint(-1) if bandSize > 1: if tempBandSize > 1: point = self.rubberBand.getPoint(0, bandSize - 2) self.tempRubberBand.movePoint(tempBandSize - 2, point) else: self.tempRubberBand.reset(QGis.Polygon) bandSize = self.rubberBand.numberOfVertices() if bandSize < 3: self.vertexMarker.hide() else: rubGeom = self.rubberBand.asGeometry() cpGeom = rubGeom.centroid() if not rubGeom.contains(cpGeom): cpGeom = rubGeom.pointOnSurface() #nearestCp = rubGeom.nearestPoint(cpGeom) self.vertexMarker.setCenter(cpGeom.asPoint()) self.derivedPoint = cpGeom.asPoint() self.vertexMarker.show() del self.capturedPoints[-1] def getCapturedPolygon(self): polygon = self.capturedPoints if len(polygon) < 3: return None else: return polygon def getDerivedPoint(self): point = self.derivedPoint if point == None: return None else: return point def getPointGeometry(self, geom): p = QgsGeometry.fromPoint(geom) if p.isGeosValid() and not p.isGeosEmpty(): return p else: return None def getPolygonGeometry(self, geom): p = QgsGeometry.fromPolygon([geom]) if p.isGeosValid() and not p.isGeosEmpty(): return p else: return None