Exemple #1
0
    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
Exemple #3
0
    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()
Exemple #4
0
    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
Exemple #5
0
 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
Exemple #6
0
    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()
Exemple #7
0
    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
Exemple #8
0
    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()