def snapToMap(self, pt): self.filter.matches = list() match = QgsMapCanvasSnappingUtils.snapToMap(self, pt, self.filter) def sorter(match): layer = match.layer() try: return self.layer_priority.index(layer) except ValueError: return 0 layer_tolerances = dict() for layer_config in self.layers(): layer_tolerances[layer_config.layer] = QgsTolerance.toleranceInProjectUnits( layer_config.tolerance, layer_config.layer, self.mapSettings(), layer_config.unit) matches = sorted(self.filter.matches, key=sorter) matches = [m for m in matches if m.distance() < layer_tolerances[m.layer()]] if matches: match = matches[0] elif self.config().mode() == QgsSnappingConfig.AdvancedConfiguration: for layer in self.layers(): if layer.type & QgsPointLocator.Area: loc = self.locatorForLayer(layer.layer) results = loc.pointInPolygon(pt) if results: return results[0] return match
def snapToMap(self, pt): match = QgsMapCanvasSnappingUtils.snapToMap(self, pt) if not match.isValid() and self.snapToMapMode() == QgsSnappingUtils.SnapAdvanced: for layer in self.layers(): if layer.type & QgsPointLocator.Area: loc = self.locatorForLayer(layer.layer) results = loc.pointInPolygon(pt) if results: return results[0] return match
class QGISRedSelectPointTool(QgsMapTool): def __init__(self, button, parent, method, type=1): QgsMapTool.__init__(self, parent.iface.mapCanvas()) self.canvas = parent.iface.mapCanvas() self.iface = parent.iface self.parent = parent self.method = method self.setAction(button) self.type = type # type 1: points; 2: lines; 3: 2-points; 4: 2-line; 5: point-line self.startMarker = QgsVertexMarker(self.iface.mapCanvas()) self.startMarker.setColor(QColor(255, 87, 51)) if self.type == 3 or self.type == 4 or self.type == 5: self.startMarker.setColor(QColor(139, 0, 0)) self.startMarker.setIconSize(15) self.startMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X if self.type == 2 or self.type == 4: self.startMarker.setIconType( QgsVertexMarker.ICON_X) # or ICON_CROSS, ICON_X self.startMarker.setPenWidth(3) self.startMarker.hide() self.endMarker = QgsVertexMarker(self.iface.mapCanvas()) self.endMarker.setColor(QColor(0, 128, 0)) self.endMarker.setIconSize(15) self.endMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X if self.type == 4 or self.type == 5: self.endMarker.setIconType( QgsVertexMarker.ICON_X) # or ICON_CROSS, ICON_X self.endMarker.setPenWidth(3) self.endMarker.hide() self.firstPoint = None self.snapper = None self.resetProperties() def activate(self): QgsMapTool.activate(self) type = 1 if self.type == 2 or self.type == 4: type = 2 self.configSnapper(type) def deactivate(self): self.resetProperties() QgsMapTool.deactivate(self) def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True """Methods""" def configSnapper(self, type): # Snapping self.snapper = QgsMapCanvasSnappingUtils(self.iface.mapCanvas()) self.snapper.setMapSettings(self.iface.mapCanvas().mapSettings()) config = QgsSnappingConfig(QgsProject.instance()) config.setType(type) # 1: Vertex; 2:Segment config.setMode(2) # All layers config.setTolerance(10) config.setUnits(1) # Pixels config.setEnabled(True) self.snapper.setConfig(config) def resetProperties(self): self.firstPoint = None self.startMarker.hide() self.endMarker.hide() self.objectSnapped = None """Events""" def canvasReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.objectSnapped is None: self.iface.messageBar().pushMessage( "Warning", "A not valid point was selected", level=1, duration=5) return if self.type == 3 or self.type == 4 or self.type == 5: if self.firstPoint is None: self.firstPoint = self.objectSnapped.point() if self.type == 5: self.configSnapper(2) else: point1 = self.firstPoint point2 = self.objectSnapped.point() # Call to parent method self.method(point1, point2) self.resetProperties() if self.type == 5: self.configSnapper(1) else: point = self.objectSnapped.point() # Call to parent method self.method(point) self.resetProperties() if event.button() == Qt.RightButton: if self.type == 3 or self.type == 5: if self.objectSnapped is None: self.iface.messageBar().pushMessage( "Warning", "A not valid point was selected", level=1, duration=5) return else: point = self.objectSnapped.point() # Call to parent method # Tconnection & Split/merge Juncitons self.method(point, None) self.resetProperties() else: self.canvas.unsetMapTool(self) self.deactivate() return def canvasMoveEvent(self, event): match = self.snapper.snapToMap(self.toMapCoordinates(event.pos())) if match.isValid(): self.objectSnapped = match if self.firstPoint is None: self.startMarker.setCenter( QgsPointXY(match.point().x(), match.point().y())) self.startMarker.show() else: self.endMarker.setCenter( QgsPointXY(match.point().x(), match.point().y())) self.endMarker.show() else: self.startMarker.hide() self.endMarker.hide() self.objectSnapped = None
class QGISRedCreatePipeTool(QgsMapTool): def __init__(self, button, iface, projectDirectory, netwName, parent): QgsMapTool.__init__(self, iface.mapCanvas()) self.iface = iface self.ProjectDirectory = projectDirectory self.NetworkName = netwName self.parent = parent self.setAction(button) self.startMarker = QgsVertexMarker(self.iface.mapCanvas()) self.startMarker.setColor(QColor(255, 87, 51)) self.startMarker.setIconSize(15) self.startMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.startMarker.setPenWidth(3) self.startMarker.hide() self.endMarker = QgsVertexMarker(self.iface.mapCanvas()) self.endMarker.setColor(QColor(255, 87, 51)) self.endMarker.setIconSize(15) self.endMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.endMarker.setPenWidth(3) self.endMarker.hide() self.snapper = None self.rubberBand1 = None self.rubberBand2 = None self.resetProperties() def activate(self): QgsMapTool.activate(self) # Snapping self.snapper = QgsMapCanvasSnappingUtils(self.iface.mapCanvas()) self.snapper.setMapSettings(self.iface.mapCanvas().mapSettings()) config = QgsSnappingConfig(QgsProject.instance()) config.setType(1) # Vertex config.setMode(2) # All layers config.setTolerance(2) config.setUnits(2) # Pixels config.setEnabled(True) self.snapper.setConfig(config) def deactivate(self): self.resetProperties() QgsMapTool.deactivate(self) def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True """Methods""" def resetProperties(self): # self.toolbarButton.setChecked(False) if self.rubberBand1 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand1) if self.rubberBand2 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand2) self.startMarker.hide() self.endMarker.hide() self.mousePoints = [] self.firstClicked = False self.objectSnapped = None self.rubberBand1 = None self.rubberBand2 = None def createRubberBand(self, points): myPoints1 = [] for p in points: myPoints1.append(QgsPoint(p.x(), p.y())) myPoints1.remove(myPoints1[-1]) if self.rubberBand1 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand1) self.rubberBand1 = QgsRubberBand(self.iface.mapCanvas(), False) self.rubberBand1.setToGeometry(QgsGeometry.fromPolyline(myPoints1), None) self.rubberBand1.setColor(QColor(240, 40, 40)) self.rubberBand1.setWidth(1) self.rubberBand1.setLineStyle(Qt.SolidLine) myPoints2 = [] myPoints2.append(QgsPoint(points[-2].x(), points[-2].y())) myPoints2.append(QgsPoint(points[-1].x(), points[-1].y())) if self.rubberBand2 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand2) self.rubberBand2 = QgsRubberBand(self.iface.mapCanvas(), False) self.rubberBand2.setToGeometry(QgsGeometry.fromPolyline(myPoints2), None) self.rubberBand2.setColor(QColor(240, 40, 40)) self.rubberBand2.setWidth(1) self.rubberBand2.setLineStyle(Qt.DashLine) """Events""" def canvasPressEvent(self, event): if event.button() == Qt.LeftButton: if not self.firstClicked: self.firstClicked = True point = self.toMapCoordinates(event.pos()) if self.objectSnapped is not None: point = self.objectSnapped.point() self.mousePoints.append(point) self.mousePoints.append(point) else: self.mousePoints.append(self.mousePoints[-1]) self.createRubberBand(self.mousePoints) if event.button() == Qt.RightButton: self.mousePoints.remove(self.mousePoints[-1]) if self.firstClicked: if (len(self.mousePoints) == 2 and self.mousePoints[0] == self.mousePoints[1]): createdPipe = False elif len(self.mousePoints) < 2: createdPipe = False else: createdPipe = True if createdPipe: self.parent.runCreatePipe(self.mousePoints) self.resetProperties() def canvasMoveEvent(self, event): # Mouse not clicked if not self.firstClicked: match = self.snapper.snapToMap(self.toMapCoordinates(event.pos())) if match.isValid(): self.objectSnapped = match self.startMarker.setCenter( QgsPointXY(match.point().x(), match.point().y())) self.startMarker.show() else: self.objectSnapped = None self.startMarker.hide() # Mouse clicked else: point = self.toMapCoordinates(event.pos()) match = self.snapper.snapToMap(point) if match.isValid(): self.objectSnapped = match self.endMarker.setCenter( QgsPointXY(match.point().x(), match.point().y())) self.endMarker.show() self.mousePoints[-1] = match.point() else: self.objectSnapped = None self.endMarker.hide() self.mousePoints[-1] = point self.createRubberBand(self.mousePoints)
class QGISRedEditLinksGeometryTool(QgsMapTool): ownMainLayers = ["Pipes", "Valves", "Pumps", "ServiceConnections"] def __init__(self, button, iface, projectDirectory, netwName): QgsMapTool.__init__(self, iface.mapCanvas()) self.iface = iface self.ProjectDirectory = projectDirectory self.NetworkName = netwName self.toolbarButton = button self.snapper = None self.vertexMarker = QgsVertexMarker(self.iface.mapCanvas()) self.vertexMarker.setColor(QColor(255, 87, 51)) self.vertexMarker.setIconSize(15) self.vertexMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.vertexMarker.setPenWidth(3) self.vertexMarker.hide() self.pipeSnapper = None self.pipeMarker = QgsVertexMarker(self.iface.mapCanvas()) self.pipeMarker.setColor(QColor(143, 0, 255)) self.pipeMarker.setIconSize(10) try: self.pipeMarker.setIconType( QgsVertexMarker.ICON_DOUBLE_TRIANGLE) # or ICON_CROSS, ICON_X except: self.pipeMarker.setIconType( QgsVertexMarker.ICON_X) # or ICON_CROSS, ICON_X self.pipeMarker.setPenWidth(3) self.pipeMarker.hide() self.mouseClicked = False self.clickedPoint = None self.objectSnapped = None self.pipeSnapped = None self.selectedFeature = None self.selectedLayer = None self.newPositionVector = QgsVector(0, 0) self.rubberBand = None self.newVertexMarker = QgsVertexMarker(self.iface.mapCanvas()) self.newVertexMarker.setColor(QColor(55, 198, 5)) self.newVertexMarker.setIconSize(15) self.newVertexMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.newVertexMarker.setPenWidth(3) self.newVertexMarker.hide() def activate(self): cursor = QCursor() cursor.setShape(Qt.ArrowCursor) self.iface.mapCanvas().setCursor(cursor) myLayers = [] # Editing layers = self.getLayers() for layer in layers: openedLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if openedLayerPath == layerPath: myLayers.append(layer) if not layer.isEditable(): layer.startEditing() # Snapping self.snapper = QgsMapCanvasSnappingUtils(self.iface.mapCanvas()) self.snapper.setMapSettings(self.iface.mapCanvas().mapSettings()) config = QgsSnappingConfig(QgsProject.instance()) config.setType(2) # Vertex config.setMode(2) # All layers config.setTolerance(10) config.setUnits(1) # Pixels config.setEnabled(True) self.snapper.setConfig(config) self.pipeSnapper = QgsMapCanvasSnappingUtils(self.iface.mapCanvas()) self.pipeSnapper.setMapSettings(self.iface.mapCanvas().mapSettings()) config = QgsSnappingConfig(QgsProject.instance()) config.setType(2) # Vertex config.setMode(2) # All layers config.setTolerance(10) config.setUnits(1) # Pixels config.setEnabled(True) self.pipeSnapper.setConfig(config) def deactivate(self): self.toolbarButton.setChecked(False) # End Editing layers = self.getLayers() for layer in layers: openedLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if openedLayerPath == layerPath: if layer.isModified(): layer.commitChanges() else: layer.rollBack() def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True """Methods""" def getUniformedPath(self, path): return QGISRedUtils().getUniformedPath(path) def getLayerPath(self, layer): return QGISRedUtils().getLayerPath(layer) def generatePath(self, folder, fileName): return QGISRedUtils().generatePath(folder, fileName) def getLayers(self): return QGISRedUtils().getLayers() def areOverlapedPoints(self, point1, point2): tolerance = 0.1 if point1.distance(point2) < tolerance: return True else: return False def isInPath(self, point1, point2, myPoint): width = point2.x() - point1.x() height = point2.y() - point1.y() widthM = myPoint.x() - point1.x() heightM = myPoint.y() - point1.y() if abs(width) >= abs(height): yEst = widthM * height / width + point1.y() if abs(yEst - myPoint.y()) < 1E-9: return True else: xEst = heightM * width / height + point1.x() if abs(xEst - myPoint.x()) < 1E-9: return True return False def createRubberBand(self, points): myPoints = points if isinstance(points[0], QgsPointXY): myPoints = [] for p in points: myPoints.append(QgsPoint(p.x(), p.y())) self.rubberBand = QgsRubberBand(self.iface.mapCanvas(), False) self.rubberBand.setToGeometry(QgsGeometry.fromPolyline(myPoints), None) self.rubberBand.setColor(QColor(55, 198, 5)) self.rubberBand.setWidth(1) self.rubberBand.setLineStyle(Qt.DashLine) self.newVertexMarker.setCenter(QgsPointXY(points[0].x(), points[0].y())) self.newVertexMarker.show() def updateRubberBand(self): newX = self.clickedPoint.x() + self.newPositionVector.x() newY = self.clickedPoint.y() + self.newPositionVector.y() self.rubberBand.movePoint(1, QgsPointXY(newX, newY)) self.newVertexMarker.setCenter(QgsPointXY(newX, newY)) def moveVertexLink(self, layer, feature, newPosition, vertexIndex): if layer.isEditable(): layer.beginEditCommand("Update link geometry") try: edit_utils = QgsVectorLayerEditUtils(layer) edit_utils.moveVertex(newPosition.x(), newPosition.y(), feature.id(), vertexIndex) except Exception as e: layer.destroyEditCommand() raise e layer.endEditCommand() def deleteVertexLink(self, layer, feature, vertexIndex): if layer.isEditable(): layer.beginEditCommand("Update link geometry") try: edit_utils = QgsVectorLayerEditUtils(layer) edit_utils.deleteVertex(feature.id(), vertexIndex) except Exception as e: layer.destroyEditCommand() raise e layer.endEditCommand() def insertVertexLink(self, layer, feature, newPoint): if layer.isEditable(): layer.beginEditCommand("Update link geometry") vertex = -1 if layer.geometryType() == 1: # Line featureGeometry = self.selectedFeature.geometry() if featureGeometry.isMultipart(): parts = featureGeometry.get() for part in parts: # only one part for i in range(len(part) - 1): if self.isInPath( QgsPointXY(part[i].x(), part[i].y()), QgsPointXY(part[i + 1].x(), part[i + 1].y()), newPoint): vertex = i + 1 try: edit_utils = QgsVectorLayerEditUtils(layer) edit_utils.insertVertex(newPoint.x(), newPoint.y(), feature.id(), vertex) except Exception as e: layer.destroyEditCommand() raise e layer.endEditCommand() """Events""" def canvasPressEvent(self, event): if self.objectSnapped is None: self.clickedPoint = None return if event.button() == Qt.RightButton: self.mouseClicked = False self.clickedPoint = None if event.button() == Qt.LeftButton: self.clickedPoint = self.objectSnapped.point() if self.vertexIndex == -1: return self.mouseClicked = True self.createRubberBand( [self.objectSnapped.point(), self.objectSnapped.point()]) def canvasMoveEvent(self, event): mousePoint = self.toMapCoordinates(event.pos()) # Mouse not clicked if not self.mouseClicked: self.pipeSnappedOn = False matchSnapper = self.snapper.snapToMap(mousePoint) if matchSnapper.isValid(): valid = False layer = matchSnapper.layer() snapLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if snapLayerPath == layerPath: valid = True if valid: self.objectSnapped = matchSnapper self.selectedLayer = layer vertex = matchSnapper.point() featureId = matchSnapper.featureId() request = QgsFeatureRequest().setFilterFid(featureId) nodes = list(layer.getFeatures(request)) self.selectedFeature = QgsFeature(nodes[0]) # #Ver aquí si es el nudo inicial y final middleNode = False self.vertexIndex = -1 if layer.geometryType() == 1: # Line featureGeometry = self.selectedFeature.geometry() if featureGeometry.isMultipart(): parts = featureGeometry.get() for part in parts: # only one part if middleNode: break i = -1 for v in part: i = i + 1 if ( i == 0 or i == len(part) - 1 ) and "ServiceConnections" not in snapLayerPath: continue matchedPoint = QgsPointXY( vertex.x(), vertex.y()) if self.areOverlapedPoints( QgsGeometry.fromPointXY( matchedPoint), QgsGeometry.fromPointXY( QgsPointXY(v.x(), v.y()))): middleNode = True self.vertexIndex = i if ( i == 0 or i == len(part) - 1 ) and "ServiceConnections" in snapLayerPath: self.pipeSnappedOn = True break if middleNode: self.vertexMarker.setCenter( QgsPointXY(vertex.x(), vertex.y())) self.vertexMarker.show() else: self.vertexMarker.hide() else: self.objectSnapped = None self.selectedFeature = None self.selectedLayer = None self.vertexMarker.hide() else: self.objectSnapped = None self.selectedFeature = None self.selectedLayer = None self.vertexMarker.hide() # Mouse clicked else: # Snap pipe layer if self.pipeSnappedOn: matchSnapper = self.pipeSnapper.snapToMap(mousePoint) if matchSnapper.isValid(): valid = False layer = matchSnapper.layer() snapLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_Pipes.shp") if snapLayerPath == layerPath: valid = True if valid: self.pipeSnapped = matchSnapper self.pipeMarker.setCenter(matchSnapper.point()) self.pipeMarker.show() else: self.pipeMarker.hide() else: self.pipeMarker.hide() self.pipeSnapped = None # # Update rubber band if self.objectSnapped is not None and self.rubberBand is not None: snappedPoint = self.objectSnapped.point() self.newPositionVector = QgsVector( mousePoint.x() - snappedPoint.x(), mousePoint.y() - snappedPoint.y()) self.updateRubberBand() def canvasReleaseEvent(self, event): if self.mouseClicked: if event.button() == 1: mousePoint = self.toMapCoordinates(event.pos()) if (self.pipeSnapped is not None): mousePoint = self.pipeSnapped.point() self.mouseClicked = False if self.objectSnapped is not None: self.moveVertexLink(self.selectedLayer, self.selectedFeature, mousePoint, self.vertexIndex) elif event.button() == 2: if self.objectSnapped is not None: self.deleteVertexLink(self.selectedLayer, self.selectedFeature, self.vertexIndex) elif event.button() == 1: if self.objectSnapped is not None: self.insertVertexLink(self.selectedLayer, self.selectedFeature, self.objectSnapped.point()) self.objectSnapped = None self.pipeSnapped = None self.selectedFeature = None self.selectedLayer = None self.vertexIndex = -1 self.iface.mapCanvas().refresh() # Remove vertex marker and rubber band self.vertexMarker.hide() self.iface.mapCanvas().scene().removeItem(self.rubberBand) self.newVertexMarker.hide() self.pipeMarker.hide()
class QgepMapToolAddReach(QgepMapToolAddFeature): """ Create a new reach with the mouse. Will snap to wastewater nodes for the first and last point and auto-connect these. """ first_snapping_match = None last_snapping_match = None last_feature_attributes = None def __init__(self, iface: QgisInterface, layer): QgepMapToolAddFeature.__init__(self, iface, layer) self.snapping_marker = None self.node_layer = QgepLayerManager.layer('vw_wastewater_node') assert self.node_layer is not None self.reach_layer = QgepLayerManager.layer('vw_qgep_reach') assert self.reach_layer is not None self.setAdvancedDigitizingAllowed(True) self.setAutoSnapEnabled(True) layer_snapping_configs = [{ 'layer': self.node_layer, 'mode': QgsSnappingConfig.Vertex }, { 'layer': self.reach_layer, 'mode': QgsSnappingConfig.VertexAndSegment }] self.snapping_configs = [] self.snapping_utils = QgsMapCanvasSnappingUtils(self.iface.mapCanvas()) for lsc in layer_snapping_configs: config = QgsSnappingConfig() config.setMode(QgsSnappingConfig.AdvancedConfiguration) config.setEnabled(True) settings = QgsSnappingConfig.IndividualLayerSettings( True, lsc['mode'], 10, QgsTolerance.Pixels) config.setIndividualLayerSettings(lsc['layer'], settings) self.snapping_configs.append(config) def left_clicked(self, event): """ The mouse is clicked: snap to neary points which are on the wastewater node layer and update the rubberband :param event: The coordinates etc. """ point3d, match = self.snap(event) if self.rubberband.numberOfVertices() == 0: self.first_snapping_match = match self.last_snapping_match = match self.rubberband.addPoint3D(point3d) self.temp_rubberband.reset() self.temp_rubberband.addPoint(QgsPointXY(point3d.x(), point3d.y())) if self.snapping_marker is not None: self.iface.mapCanvas().scene().removeItem(self.snapping_marker) self.snapping_marker = None def mouse_move(self, event): _, match = self.snap(event) # snap indicator if not match.isValid(): if self.snapping_marker is not None: self.iface.mapCanvas().scene().removeItem(self.snapping_marker) self.snapping_marker = None return # TODO QGIS 3: see if vertices can be removed # we have a valid match if self.snapping_marker is None: self.snapping_marker = QgsVertexMarker(self.iface.mapCanvas()) self.snapping_marker.setPenWidth(3) self.snapping_marker.setColor(QColor(Qt.magenta)) if match.hasVertex(): if match.layer(): icon_type = QgsVertexMarker.ICON_BOX # vertex snap else: icon_type = QgsVertexMarker.ICON_X # intersection snap else: icon_type = QgsVertexMarker.ICON_DOUBLE_TRIANGLE # must be segment snap self.snapping_marker.setIconType(icon_type) self.snapping_marker.setCenter(match.point()) def snap(self, event): """ Snap to nearby points on the wastewater node layer which may be used as connection points for this reach. :param event: The mouse event :return: The snapped position in map coordinates """ for config in self.snapping_configs: self.snapping_utils.setConfig(config) match = self.snapping_utils.snapToMap( QgsPointXY(event.originalMapPoint())) if match.isValid(): return QgsPoint(match.point()), match # if no match, snap to all layers (according to map settings) and try to grab Z match = self.iface.mapCanvas().snappingUtils().snapToMap( QgsPointXY(event.originalMapPoint())) if match.isValid() and match.hasVertex(): if match.layer(): req = QgsFeatureRequest(match.featureId()) f = next(match.layer().getFeatures(req)) assert f.isValid() (ok, vertex_id) = f.geometry().vertexIdFromVertexNr( match.vertexIndex()) assert ok point = f.geometry().constGet().vertexAt(vertex_id) assert type(point) == QgsPoint return point, match else: return QgsPoint(match.point()), match return QgsPoint(event.originalMapPoint()), match def right_clicked(self, _): """ The party is over, the reach digitized. Create a feature from the rubberband and show the feature form. """ self.temp_rubberband.reset() if self.snapping_marker is not None: self.iface.mapCanvas().scene().removeItem(self.snapping_marker) self.snapping_marker = None if len(self.rubberband.points) >= 2: fields = self.layer.fields() f = QgsFeature(fields) if not self.last_feature_attributes: self.last_feature_attributes = [None] * fields.count() for idx, field in enumerate(fields): if field.name() in [ 'clear_height', 'material', 'ch_usage_current', 'ch_function_hierarchic', 'ch_function_hydraulic', 'horizontal_positioning', 'ws_status', 'ws_year_of_construction', 'ws_fk_owner', 'ws_fk_operator', 'inside_coating', 'fk_pipe_profile', 'remark' ]: f.setAttribute(idx, self.last_feature_attributes[idx]) else: # try client side default value first v = self.layer.defaultValue(idx, f) if v != NULL: f.setAttribute(idx, v) else: f.setAttribute( idx, self.layer.dataProvider().defaultValue(idx)) f.setGeometry(self.rubberband.asGeometry3D()) snapping_results = { 'from': self.first_snapping_match, 'to': self.last_snapping_match } for dest, match in list(snapping_results.items()): level_field_index = self.layer.fields().indexFromName( 'rp_{dest}_level'.format(dest=dest)) pt_idx = 0 if dest == 'from' else -1 if match.isValid() and match.layer() in (self.node_layer, self.reach_layer): request = QgsFeatureRequest(match.featureId()) network_element = next(match.layer().getFeatures(request)) assert network_element.isValid() # set the related network element field = self.layer.fields().indexFromName( 'rp_{dest}_fk_wastewater_networkelement'.format( dest=dest)) f.setAttribute(field, network_element.attribute('obj_id')) # assign level if the match is a node or if we have 3D from snapping if match.layer() == self.node_layer: level = network_element['bottom_level'] f.setAttribute(level_field_index, level) elif self.rubberband.points[pt_idx].z() != 0: level = self.rubberband.points[pt_idx].z() level = level if not math.isnan(level) else NULL f.setAttribute(level_field_index, level) dlg = self.iface.getFeatureForm(self.layer, f) dlg.setMode(QgsAttributeEditorContext.AddFeatureMode) dlg.exec_() self.last_feature_attributes = dlg.feature().attributes() self.rubberband.reset3D()
class QgepMapTool(QgsMapTool): """ Base class for all the map tools """ highlightedPoints = [] logger = logging.getLogger(__name__) snapper = None def __init__(self, iface: QgisInterface, button, network_analyzer: QgepGraphManager = None): QgsMapTool.__init__(self, iface.mapCanvas()) self.canvas = iface.mapCanvas() self.cursor = QCursor(Qt.CrossCursor) self.button = button self.msgBar = iface.messageBar() self.network_analyzer = network_analyzer settings = QSettings() current_profile_color = settings.value( "/QGEP/CurrentProfileColor", '#FF9500') self.rubberBand = QgsRubberBand(self.canvas) self.rubberBand.setColor(QColor(current_profile_color)) self.rubberBand.setWidth(3) def activate(self): """ Gets called when the tool is activated """ QgsMapTool.activate(self) self.canvas.setCursor(self.cursor) self.button.setChecked(True) def deactivate(self): """ Gets called whenever the tool is deactivated directly or indirectly """ QgsMapTool.deactivate(self) self.button.setChecked(False) # pylint: disable=no-self-use def isZoomTool(self): """ Will return if this is a zoom tool """ return False def setCursor(self, cursor): """ Set the cursor for this maptool """ self.cursor = QCursor(cursor) # =========================================================================== # Events # =========================================================================== def canvasReleaseEvent(self, event): """ Issues rightClicked and leftClicked events """ if event.button() == Qt.RightButton: self.rightClicked(event) else: self.leftClicked(event) def canvasDoubleClickEvent(self, event): """ Forwards to doubleClicked """ try: self.doubleClicked(event) except AttributeError: pass # =========================================================================== # Snapping # =========================================================================== def init_snapper(self): """ Initialize snapper """ if not self.snapper: self.node_layer = self.network_analyzer.getNodeLayer() self.snapper = QgsMapCanvasSnappingUtils(self.canvas) config = QgsSnappingConfig() config.setMode(QgsSnappingConfig.AdvancedConfiguration) config.setEnabled(True) ils = QgsSnappingConfig.IndividualLayerSettings(True, QgsSnappingConfig.VertexAndSegment, 16, QgsTolerance.Pixels) config.setIndividualLayerSettings(self.node_layer, ils) self.snapper.setConfig(config) def snap_point(self, event, show_menu: bool = True) -> QgsPointLocator.Match: """ Snap to a point on this network :param event: A QMouseEvent :param show_menu: determines if a menu shall be shown on a map if several matches are available """ clicked_point = event.pos() if not self.snapper: self.init_snapper() match_filter = CounterMatchFilter() match = self.snapper.snapToMap(clicked_point, match_filter) if not match.isValid() or len(match_filter.matches) == 1: return match elif len(match_filter.matches) > 1: matches_by_id = {match.featureId(): match for match in match_filter.matches} node_features = self.network_analyzer.getFeaturesById(self.network_analyzer.getNodeLayer(), list(matches_by_id.keys())) # Filter wastewater nodes filtered_features = { fid: node_features.featureById(fid) for fid in node_features.asDict() if node_features.attrAsUnicode(node_features.featureById(fid), 'type') == 'wastewater_node' } # Only one wastewater node left: return this if len(filtered_features) == 1: matches = (match for match in match_filter.matches if match.featureId() == next(iter(filtered_features.keys()))) return next(matches) # Still not sure which point to take? # Are there no wastewater nodes filtered? Let the user choose from the reach points if not filtered_features: filtered_features = node_features.asDict() # Ask the user which point he wants to use if not show_menu: return QgsPointLocator.Match() actions = dict() menu = QMenu(self.canvas) for fid, feature in filtered_features.items(): try: title = feature.attribute('description') + " (" + feature.attribute('obj_id') + ")" except TypeError: title = " (" + feature.attribute('obj_id') + ")" actions[QAction(title, menu)] = matches_by_id[fid] for action in sorted(actions.keys(), key=lambda o: o.text()): menu.addAction(action) clicked_action = menu.exec_(self.canvas.mapToGlobal(event.pos())) if clicked_action is not None: return actions[clicked_action] return QgsPointLocator.Match()
class QGISRedMoveNodesTool(QgsMapTool): ownMainLayers = [ "Pipes", "Valves", "Pumps", "Junctions", "Tanks", "Reservoirs", "Demands", "Sources" ] myNodeLayers = ["Junctions", "Tanks", "Reservoirs", "Demands", "Sources"] def __init__(self, button, iface, projectDirectory, netwName): QgsMapTool.__init__(self, iface.mapCanvas()) self.iface = iface self.ProjectDirectory = projectDirectory self.NetworkName = netwName self.toolbarButton = button self.snapper = None self.vertexMarker = QgsVertexMarker(self.iface.mapCanvas()) self.vertexMarker.setColor(QColor(255, 87, 51)) self.vertexMarker.setIconSize(15) self.vertexMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.vertexMarker.setPenWidth(3) self.vertexMarker.hide() self.mousePoint = None self.mouseClicked = False self.clickedPoint = None self.objectSnapped = None self.selectedNodeFeature = None self.selectedNodeLayer = None self.adjacentFeatures = None self.newPositionVector = QgsVector(0, 0) self.rubberBand = None self.newVertexMarker = QgsVertexMarker(self.iface.mapCanvas()) self.newVertexMarker.setColor(QColor(55, 198, 5)) self.newVertexMarker.setIconSize(15) self.newVertexMarker.setIconType( QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X self.newVertexMarker.setPenWidth(3) self.newVertexMarker.hide() def activate(self): cursor = QCursor() cursor.setShape(Qt.ArrowCursor) self.iface.mapCanvas().setCursor(cursor) myLayers = [] # Editing layers = self.getLayers() for layer in layers: openedLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if openedLayerPath == layerPath: myLayers.append(layer) if not layer.isEditable(): layer.startEditing() # Snapping self.snapper = QgsMapCanvasSnappingUtils(self.iface.mapCanvas()) self.snapper.setMapSettings(self.iface.mapCanvas().mapSettings()) config = QgsSnappingConfig(QgsProject.instance()) config.setType(1) # Vertex config.setMode(2) # All layers config.setTolerance(1) config.setUnits(2) # Pixels config.setEnabled(True) self.snapper.setConfig(config) def deactivate(self): self.toolbarButton.setChecked(False) # End Editing layers = self.getLayers() for layer in layers: openedLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if openedLayerPath == layerPath: if layer.isModified(): layer.commitChanges() else: layer.rollBack() def isZoomTool(self): return False def isTransient(self): return False def isEditTool(self): return True """Methods""" def getUniformedPath(self, path): return QGISRedUtils().getUniformedPath(path) def getLayerPath(self, layer): return QGISRedUtils().getLayerPath(layer) def generatePath(self, folder, fileName): return QGISRedUtils().generatePath(folder, fileName) def getLayers(self): return QGISRedUtils().getLayers() def findAdjacentElements(self, nodeGeometry): adjacentElements = {} layers = self.getLayers() for layer in layers: openedLayerPath = self.getLayerPath(layer) for name in self.ownMainLayers: layePath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if openedLayerPath == layePath: adjacentFeatures = [] for feature in layer.getFeatures(): featureGeometry = feature.geometry() if layer.geometryType() == 0: # Point if self.areOverlapedPoints(nodeGeometry, featureGeometry): adjacentFeatures.append(feature) elif layer.geometryType() == 1: if featureGeometry.isMultipart(): for part in featureGeometry.get( ): # only one part first_vertex = part[0] last_vertex = part[-1] else: first_vertex = featureGeometry.get()[0] last_vertex = featureGeometry.get()[-1] firsVertex = QgsGeometry.fromPointXY( QgsPointXY(first_vertex.x(), first_vertex.y())) lastVertex = QgsGeometry.fromPointXY( QgsPointXY(last_vertex.x(), last_vertex.y())) if self.areOverlapedPoints(nodeGeometry, firsVertex) or\ self.areOverlapedPoints(nodeGeometry, lastVertex): adjacentFeatures.append(feature) if len(adjacentFeatures) > 0: adjacentElements[layer] = adjacentFeatures return adjacentElements def areOverlapedPoints(self, point1, point2): tolerance = 0.1 if point1.distance(point2) < tolerance: return True else: return False def createRubberBand(self, points): myPoints = points if isinstance(points[0], QgsPointXY): myPoints = [] for p in points: myPoints.append(QgsPoint(p.x(), p.y())) self.rubberBand = QgsRubberBand(self.iface.mapCanvas(), False) self.rubberBand.setToGeometry(QgsGeometry.fromPolyline(myPoints), None) self.rubberBand.setColor(QColor(55, 198, 5)) self.rubberBand.setWidth(1) self.rubberBand.setLineStyle(Qt.DashLine) self.newVertexMarker.setCenter(QgsPointXY(points[0].x(), points[0].y())) self.newVertexMarker.show() def updateRubberBand(self): newX = self.clickedPoint.x() + self.newPositionVector.x() newY = self.clickedPoint.y() + self.newPositionVector.y() self.rubberBand.movePoint(1, QgsPointXY(newX, newY)) self.newVertexMarker.setCenter(QgsPointXY(newX, newY)) def moveNodePoint(self, layer, nodeFeature, newPosition): if layer.isEditable(): layer.beginEditCommand('Move node') try: edit_utils = QgsVectorLayerEditUtils(layer) edit_utils.moveVertex(newPosition.x(), newPosition.y(), nodeFeature.id(), 0) except Exception as e: layer.destroyEditCommand() raise e layer.endEditCommand() def moveVertexLink(self, layer, feature, newPosition, vertexIndex): if layer.isEditable(): layer.beginEditCommand("Update link geometry") try: edit_utils = QgsVectorLayerEditUtils(layer) edit_utils.moveVertex(newPosition.x(), newPosition.y(), feature.id(), vertexIndex) except Exception as e: layer.destroyEditCommand() raise e layer.endEditCommand() """Events""" def canvasPressEvent(self, event): if self.objectSnapped is None: self.clickedPoint = None return if event.button() == Qt.RightButton: self.mouseClicked = False self.clickedPoint = None if event.button() == Qt.LeftButton: self.mouseClicked = True self.clickedPoint = self.objectSnapped.point() self.selectedNodeFeature = None self.adjacentFeatures = None foundNode = False layers = self.getLayers() for layer in layers: openedLayerPath = self.getLayerPath(layer) for name in self.myNodeLayers: layerPath = self.generatePath( self.ProjectDirectory, self.NetworkName + "_" + name + ".shp") if openedLayerPath == layerPath: locatedPoint = self.snapper.locatorForLayer(layer) match = locatedPoint.nearestVertex( self.objectSnapped.point(), 1) if match.isValid(): featureId = match.featureId() request = QgsFeatureRequest().setFilterFid( featureId) node = list(layer.getFeatures(request)) self.selectedNodeLayer = layer foundNode = True if not foundNode: return self.selectedNodeFeature = QgsFeature(node[0]) self.adjacentFeatures = self.findAdjacentElements( self.selectedNodeFeature.geometry()) self.createRubberBand( [self.objectSnapped.point(), self.objectSnapped.point()]) def canvasMoveEvent(self, event): self.mousePoint = self.toMapCoordinates(event.pos()) # Mouse not clicked if not self.mouseClicked: match = self.snapper.snapToMap(self.mousePoint) if match.isValid(): self.objectSnapped = match vertex = match.point() self.vertexMarker.setCenter(QgsPointXY(vertex.x(), vertex.y())) self.vertexMarker.show() else: self.objectSnapped = None self.selectedNodeFeature = None self.vertexMarker.hide() # Mouse clicked else: # Update rubber band if self.objectSnapped is not None and self.rubberBand is not None: snappedPoint = self.objectSnapped.point() self.newPositionVector = QgsVector( self.mousePoint.x() - snappedPoint.x(), self.mousePoint.y() - snappedPoint.y()) self.updateRubberBand() def canvasReleaseEvent(self, event): mousePoint = self.toMapCoordinates(event.pos()) if not self.mouseClicked: return if event.button() == 1: self.mouseClicked = False if self.objectSnapped is not None: if self.selectedNodeFeature is not None: for adjLayer in self.adjacentFeatures: for feature in self.adjacentFeatures[adjLayer]: if adjLayer.geometryType() == 0: # Point self.moveNodePoint(adjLayer, feature, mousePoint) else: nodeGeometry = self.selectedNodeFeature.geometry( ) featureGeometry = feature.geometry() if featureGeometry.isMultipart(): for part in featureGeometry.get( ): # only one part firstVertex = part[0] vertices = len(part) else: firstVertex = featureGeometry.get()[0] vertices = 2 firstPoint = QgsGeometry.fromPointXY( QgsPointXY(firstVertex.x(), firstVertex.y())) if self.areOverlapedPoints( nodeGeometry, firstPoint): index = 0 else: index = vertices - 1 self.moveVertexLink(adjLayer, feature, mousePoint, index) self.objectSnapped = None self.selectedNodeFeature = None self.iface.mapCanvas().refresh() # Remove vertex marker and rubber band self.vertexMarker.hide() self.iface.mapCanvas().scene().removeItem(self.rubberBand) self.newVertexMarker.hide()