def canvasPressEvent(self, event): # pylint: disable=missing-docstring if event.button() == Qt.MiddleButton: return elif event.button() == Qt.RightButton: self.cancel() return if self.is_active: if self.modified: self.handler.end_edit_group() self.report_success() self.finalize_operation() elif event.button() == Qt.LeftButton: matches = self.get_district_boundary_matches(event.mapPoint()) districts = self.get_districts_from_matches(matches) valid = False if not self.matches_are_valid_for_boundary(matches): match = self.get_district_area_match(event.mapPoint()) districts = self.get_districts_from_matches([match]) if districts: valid = True else: valid = True self.current_district = None self.modified = set() if valid: self.is_active = True self.click_point = event.mapPoint() self.snap_indicator.setMatch(QgsPointLocator.Match()) self.districts = districts self.handler.begin_operation() if self.decorator_factory is not None: self.pop_decorator = self.decorator_factory.create_decorator(self.canvas()) self.canvas().update()
def snap_to_segment(self, pos): """ Temporarily override snapping config and snap to vertices and edges of any editable vector layer, to allow selection of node for editing (if snapped to edge, it would offer creation of a new vertex there). """ map_point = self.toMapCoordinates(pos) tol = QgsTolerance.vertexSearchRadius(self.canvas.mapSettings()) snap_type = QgsPointLocator.Type(QgsPointLocator.Edge) snap_layers = [] for layer in self.canvas.layers(): if not isinstance(layer, QgsVectorLayer): continue snap_layers.append( QgsSnappingUtils.LayerConfig(layer, snap_type, tol, QgsTolerance.ProjectUnits)) snap_util = self.canvas.snappingUtils() old_layers = snap_util.layers() old_mode = snap_util.snapToMapMode() old_inter = snap_util.snapOnIntersections() snap_util.setLayers(snap_layers) snap_util.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) snap_util.setSnapOnIntersections(False) m = snap_util.snapToMap(map_point) snap_util.setLayers(old_layers) snap_util.setSnapToMapMode(old_mode) snap_util.setSnapOnIntersections(old_inter) return m
def canvasMoveEvent(self, event): # pylint: disable=missing-docstring if not self.is_active: # snapping tool - show indicator matches = self.get_district_boundary_matches(event.mapPoint()) if self.matches_are_valid_for_boundary(matches): # we require exactly 2 matches from different districts -- cursor must be over a border # of two features self.snap_indicator.setMatch(matches[0]) else: self.snap_indicator.setMatch(QgsPointLocator.Match()) elif self.districts: dist = self.click_point.distance(event.mapPoint()) if dist < QgsTolerance.vertexSearchRadius(self.canvas().mapSettings()): return match = self.get_district_area_match(event.mapPoint()) p = QgsGeometry.fromPointXY(event.mapPoint()) targets = [m for m in self.get_target_features_from_matches([match]) if m.id() not in self.modified and m.geometry().intersects(p)] if len(targets) == 1: target = targets[0] old_district = target[self.handler.target_field] if not self.current_district: candidates = [d for d in self.districts if d != old_district] if candidates: self.current_district = candidates[0] if self.current_district and old_district and self.current_district != old_district: if not self.modified: # first modified district - push edit command self.handler.begin_edit_group( QCoreApplication.translate('LinzRedistrict', 'Redistrict to {}').format( self.district_registry.get_district_title(self.current_district))) self.modified.add(target.id()) self.handler.assign_district([target.id()], self.current_district) AudioUtils.play_redistrict_sound()
def snap_to_dimension_layer(self, pos): """ Temporarily override snapping config and snap to vertices and edges of any editable vector layer, to allow selection of node for editing (if snapped to edge, it would offer creation of a new vertex there). """ map_point = self.toMapCoordinates(pos) tol = QgsTolerance.vertexSearchRadius(self.canvas().mapSettings()) snap_type = QgsPointLocator.Type(QgsPointLocator.Edge) snap_layers = [ QgsSnappingUtils.LayerConfig(self.layer, snap_type, tol, QgsTolerance.ProjectUnits) ] snap_util = self.canvas().snappingUtils() old_layers = snap_util.layers() old_mode = snap_util.snapToMapMode() old_inter = snap_util.snapOnIntersections() snap_util.setLayers(snap_layers) snap_util.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) snap_util.setSnapOnIntersections(False) m = snap_util.snapToMap(map_point) snap_util.setLayers(old_layers) snap_util.setSnapToMapMode(old_mode) snap_util.setSnapOnIntersections(old_inter) f = QgsFeature() if m.featureId() is not None: self.layer.getFeatures(QgsFeatureRequest().setFilterFid( m.featureId())).nextFeature(f) return f
def getLayersSettings(mapCanvas, types, snapType=None): """ To get the snapping config from different layers :param mapCanvas: the used QgsMapCanvas :param types: geometry types in use :param snapType: snapping type :return: list of layers config """ snap_layers = [] for layer in mapCanvas.layers(): if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType( ) in types: snap_util = mapCanvas.snappingUtils() mode = snap_util.snapToMapMode() if mode == QgsSnappingUtils.SnapCurrentLayer and layer.id( ) != mapCanvas.currentLayer().id(): continue if mode == QgsSnappingUtils.SnapAllLayers: snap_index, tolerance, unitType = snap_util.defaultSettings( ) snap_type = QgsPointLocator.Type(snap_index) else: noUse, enabled, snappingType, unitType, tolerance, avoidIntersection = \ QgsProject.instance().snapSettingsForLayer(layer.id()) if layer.type() == QgsMapLayer.VectorLayer and enabled: if snapType is None: if snappingType == QgsSnapper.SnapToVertex: snap_type = QgsPointLocator.Vertex elif snappingType == QgsSnapper.SnapToSegment: snap_type = QgsPointLocator.Edge elif snappingType == QgsSnapper.SnapToVertexAndSegment: snap_type = QgsPointLocator.Edge and QgsPointLocator.Vertex else: snap_type = QgsPointLocator.All else: snap_type = snapType else: continue snap_layers.append( QgsSnappingUtils.LayerConfig(layer, snap_type, tolerance, unitType)) return snap_layers
def testInteraction(self): # pylint: disable=too-many-statements """ Test tool interaction """ canvas = QgsMapCanvas() canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326)) canvas.setFrameStyle(0) canvas.resize(600, 400) self.assertEqual(canvas.width(), 600) self.assertEqual(canvas.height(), 400) layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string", "layer", "memory") f = QgsFeature() f.setAttributes(['a']) f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 32, 15, 45))) f2 = QgsFeature() f2.setAttributes(['b']) f2.setGeometry(QgsGeometry.fromRect(QgsRectangle(15, 25, 18, 45))) success, (f, f2) = layer.dataProvider().addFeatures([f, f2]) self.assertTrue(success) canvas.setLayers([layer]) canvas.setExtent(QgsRectangle(10, 30, 20, 35)) canvas.show() handler = RedistrictHandler(layer, 'fldtxt') factory = DecoratorFactory() registry = DistrictRegistry(districts=['a', 'b']) tool = InteractiveRedistrictingTool(canvas, handler, district_registry=registry, decorator_factory=factory) # mouse over a feature's interior point = canvas.mapSettings().mapToPixel().transform(20, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertFalse(tool.is_active) self.assertFalse(tool.snap_indicator.match().isValid()) # mouse over a single feature's boundary (not valid district boundary) point = canvas.mapSettings().mapToPixel().transform(5, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertFalse(tool.is_active) self.assertFalse(tool.snap_indicator.match().isValid()) # mouse over a two feature's boundary (valid district boundary) point = canvas.mapSettings().mapToPixel().transform(15, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertFalse(tool.is_active) self.assertTrue(tool.snap_indicator.match().isValid()) # avoid segfault tool.snap_indicator.setMatch(QgsPointLocator.Match()) # clicks to ignore point = canvas.mapSettings().mapToPixel().transform(10, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.MidButton) tool.canvasPressEvent(event) self.assertFalse(tool.is_active) event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.RightButton) tool.canvasPressEvent(event) self.assertFalse(tool.is_active) # click over bad area point = canvas.mapSettings().mapToPixel().transform(10, 30) event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.LeftButton) tool.canvasPressEvent(event) self.assertFalse(tool.is_active) # click over feature area layer.startEditing() point = canvas.mapSettings().mapToPixel().transform(10, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.LeftButton) tool.canvasPressEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.click_point.x(), 10) self.assertEqual(tool.click_point.y(), 33) self.assertEqual(tool.districts, {'a'}) self.assertFalse(tool.modified) # now move over current feature - should do nothing! point = canvas.mapSettings().mapToPixel().transform(10, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertTrue(tool.is_active) self.assertFalse(tool.modified) # move over other feature self.assertEqual(layer.getFeature(f2.id())[0], 'b') point = canvas.mapSettings().mapToPixel().transform(16, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.modified, {f2.id()}) self.assertEqual(tool.current_district, 'a') self.assertEqual(layer.getFeature(f2.id())[0], 'a') # move over nothing point = canvas.mapSettings().mapToPixel().transform(26, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.modified, {f2.id()}) # left click - commit changes event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.LeftButton) tool.canvasPressEvent(event) self.assertFalse(tool.is_active) layer.rollBack() layer.startEditing() # add a decorator tool.decorator_factory = TestDecoratorFactory() # now try with clicks over boundary point = canvas.mapSettings().mapToPixel().transform(15, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.LeftButton) tool.canvasPressEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.click_point.x(), 15) self.assertEqual(tool.click_point.y(), 33) self.assertEqual(tool.districts, {'a', 'b'}) self.assertFalse(tool.modified) # move left self.assertEqual(layer.getFeature(f.id())[0], 'a') self.assertEqual(layer.getFeature(f2.id())[0], 'b') point = canvas.mapSettings().mapToPixel().transform(10, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.modified, {f.id()}) self.assertEqual(tool.current_district, 'b') self.assertEqual(layer.getFeature(f.id())[0], 'b') self.assertEqual(layer.getFeature(f2.id())[0], 'b') # move over nothing point = canvas.mapSettings().mapToPixel().transform(26, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.modified, {f.id()}) # right click - discard changes event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.RightButton) tool.canvasPressEvent(event) self.assertFalse(tool.is_active) self.assertEqual(layer.getFeature(f.id())[0], 'a') self.assertEqual(layer.getFeature(f2.id())[0], 'b') # try again, move right point = canvas.mapSettings().mapToPixel().transform(15, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.LeftButton) tool.canvasPressEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.click_point.x(), 15) self.assertEqual(tool.click_point.y(), 33) self.assertEqual(tool.districts, {'a', 'b'}) self.assertFalse(tool.modified) # move right self.assertEqual(layer.getFeature(f.id())[0], 'a') self.assertEqual(layer.getFeature(f2.id())[0], 'b') point = canvas.mapSettings().mapToPixel().transform(17, 33) event = QgsMapMouseEvent(canvas, QEvent.MouseMove, QPoint(point.x(), point.y())) tool.canvasMoveEvent(event) self.assertTrue(tool.is_active) self.assertEqual(tool.modified, {f2.id()}) self.assertEqual(tool.current_district, 'a') self.assertEqual(layer.getFeature(f.id())[0], 'a') self.assertEqual(layer.getFeature(f2.id())[0], 'a') event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress, QPoint(point.x(), point.y()), Qt.RightButton) tool.canvasPressEvent(event) self.assertFalse(tool.is_active) self.assertEqual(layer.getFeature(f.id())[0], 'a') self.assertEqual(layer.getFeature(f2.id())[0], 'b') layer.rollBack()
def snap_to_editable_layer(self, e): """ Temporarily override snapping config and snap to vertices and edges of any editable vector layer, to allow selection of node for editing (if snapped to edge, it would offer creation of a new vertex there). """ QgsMessageLog.logMessage("In TOMsNodeTool:snap_to_editable_layer", tag="TOMs panel") map_point = self.toMapCoordinates(e.pos()) tol = QgsTolerance.vertexSearchRadius(self.canvas().mapSettings()) snap_type = QgsPointLocator.Type(QgsPointLocator.Vertex|QgsPointLocator.Edge) #snap_layers = [] ### TH: Amend to choose only from selected feature (and layer) """snap_layers.append(QgsSnappingUtils.LayerConfig( self.origLayer, snap_type, tol, QgsTolerance.ProjectUnits))""" """for layer in self.canvas().layers(): if not isinstance(layer, QgsVectorLayer) or not layer.isEditable(): continue snap_layers.append(QgsSnappingUtils.LayerConfig( layer, snap_type, tol, QgsTolerance.ProjectUnits))""" snap_util = self.canvas().snappingUtils() old_snap_util = snap_util snap_config = snap_util.config() #snap_util = QgsSnappingUtils() #snap_config = snap_util.config() # old_layers = snap_util.layers() # old_mode = snap_util.mode() # old_intersections = old_snap_config.intersectionSnapping() """ for layer in snap_config.individualLayerSettings().keys(): snap_config.removeLayers([layer]) """ snap_util.setCurrentLayer(self.origLayer) snap_config.setMode(QgsSnappingConfig.ActiveLayer) snap_config.setIntersectionSnapping(False) # only snap to layers #m = snap_util.snapToMap(map_point) snap_config.setTolerance(tol) snap_config.setUnits(QgsTolerance.ProjectUnits) snap_config.setType(QgsSnappingConfig.VertexAndSegment) snap_config.setEnabled(True) """snap_config.setMode(QgsSnappingConfig.AdvancedConfiguration) currLayerSnapSettings = snap_config.individualLayerSettings(self.origLayer) currLayerSnapSettings.setTolerance(tol) currLayerSnapSettings.setUnits(QgsTolerance.ProjectUnits) currLayerSnapSettings.setType(QgsSnappingConfig.VertexAndSegment) currLayerSnapSettings.setEnabled(True) snap_config.setIndividualLayerSettings(self.origLayer, currLayerSnapSettings)""" # try to stay snapped to previously used feature # so the highlight does not jump around at nodes where features are joined ### TH: Amend to choose only from selected feature (and layer) filter_last = OneFeatureFilter(self.origLayer, self.origFeature.getFeature().id()) # m = snap_util.snapToMap(map_point, filter_last) """if m_last.isValid() and m_last.distance() <= m.distance(): m = m_last""" self.origFeature.printFeature() QgsMessageLog.logMessage("In TOMsNodeTool:snap_to_editable_layer: origLayer " + self.origLayer.name(), tag="TOMs panel") """ v3 try to use some other elements of snap_config - snapToCurrentLayer - setCurrentLayer """ QgsMessageLog.logMessage("In TOMsNodeTool:snap_to_editable_layer: pos " + str(e.pos().x()) + "|" + str(e.pos().y()), tag="TOMs panel") m = snap_util.snapToCurrentLayer(e.pos(), snap_type, filter_last) """self.canvas().setSnappingUtils(snap_util) m = snap_util.snapToMap(e.pos(), filter_last)""" #snap_util.setLayers(old_layers) #snap_config.setMode(old_mode) #snap_config.setIntersectionSnapping(old_intersections) self.canvas().setSnappingUtils(old_snap_util) #self.last_snap = m # TODO: Tidy up ... QgsMessageLog.logMessage("In TOMsNodeTool:snap_to_editable_layer: snap point " + str(m.type()) +";" + str(m.isValid()) + "; ", tag="TOMs panel") return m
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()