Exemplo n.º 1
0
class FilletDigitizingMode(QgsMapToolEmitPoint):

    MESSAGE_HEADER = "Fillet"
    DEFAULT_FILLET_RADIUS = 0

    def __init__(self, iface):
        self.iface = iface
        QgsMapToolEmitPoint.__init__(self, self.iface.mapCanvas())

        self.messageBarUtils = MessageBarUtils(iface)
        self.segmentFinderTool = SegmentFinderTool(self.iface.mapCanvas())
        self.rubberBandSegment1 = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandSegment1.setColor(QColor(255, 0, 0))
        self.rubberBandSegment1.setWidth(2)

        self.layer = None
        self.reset()

    def setLayer(self, layer):
        self.layer = layer

    def reset(self, clearMessages=True):
        self.step = 0

        self.segment1 = None
        self.segment2 = None

        self.rubberBandSegment1.reset(QGis.Line)

        try:
            self.segmentFinderTool.segmentFound.disconnect()
        except Exception:
            pass

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

    def resetFillet(self):
        self.reset(False)

    def deactivate(self):
        self.reset()
        QgsMapToolEmitPoint.deactivate(self)

    def next(self):
        if self.step == 0:
            self.messageBarUtils.showButton(
                FilletDigitizingMode.MESSAGE_HEADER,
                "Select first segment",
                "Done",
                buttonCallback=self.done)
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.segment1Found)

        elif self.step == 1:

            filletRadius, found = QgsProject.instance().readEntry(
                constants.SETTINGS_KEY, constants.SETTINGS_FILLET_RADIUS, None)

            if not found:
                filletRadius = str(FilletDigitizingMode.DEFAULT_FILLET_RADIUS)

            _, lineEdit = self.messageBarUtils.showLineEdit(
                FilletDigitizingMode.MESSAGE_HEADER,
                "Select second segment and set radius:", filletRadius)
            self.lineEditFilletRadius = lineEdit

            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.segment2Found)

        elif self.step == 2:
            self.filletRadius = self.lineEditFilletRadius.text()

            self.doFillet()

    def segment1Found(self, result, pointClicked):
        self.segment1 = result
        self.segmentFinderTool.segmentFound.disconnect(self.segment1Found)
        self.segmentFinderTool.deactivate()

        self.rubberBandSegment1.reset(QGis.Line)
        self.rubberBandSegment1.addPoint(result.beforeVertex)
        self.rubberBandSegment1.addPoint(result.afterVertex, True)
        self.rubberBandSegment1.show()

        self.step = 1
        self.next()

    def segment2Found(self, result, pointClicked):
        self.segment2 = result
        self.segmentFinderTool.segmentFound.disconnect(self.segment2Found)
        self.segmentFinderTool.deactivate()

        self.step = 2
        self.next()

    def doFillet(self):
        self.messageBarUtils.showMessage(FilletDigitizingMode.MESSAGE_HEADER,
                                         "Running...",
                                         QgsMessageBar.INFO,
                                         duration=0)

        try:

            crsDest = self.layer.crs()
            canvas = self.iface.mapCanvas()
            mapRenderer = canvas.mapSettings()
            crsSrc = mapRenderer.destinationCrs()
            crsTransform = QgsCoordinateTransform(crsSrc, crsDest)

            self.layer.beginEditCommand(FilletDigitizingMode.MESSAGE_HEADER)

            try:
                filletRadius = float(self.filletRadius)
                # save for next time
                QgsProject.instance().writeEntry(
                    constants.SETTINGS_KEY, constants.SETTINGS_FILLET_RADIUS,
                    filletRadius)
            except ValueError:
                filletRadius = FilletDigitizingMode.DEFAULT_FILLET_RADIUS

            # do initial computations in map canvas coordinates
            # check intersection

            extendUtils = ExtendUtils(self.iface)
            ip = extendUtils.intersectionPoint(self.segment1.beforeVertex,
                                               self.segment1.afterVertex,
                                               self.segment2.beforeVertex,
                                               self.segment2.afterVertex)

            if ip == None:
                self.messageBarUtils.showMessage(
                    FilletDigitizingMode.MESSAGE_HEADER,
                    "The 2 segments do not intersect. Nothing was done.",
                    QgsMessageBar.WARNING,
                    duration=5)

                self.layer.destroyEditCommand()
                return

            if filletRadius == 0:
                # reproject to layer coordinates
                ip = crsTransform.transform(ip)

                # just extend the 2 lines
                ok1 = self.extend(ip, self.segment1, extendUtils)
                ok2 = self.extend(ip, self.segment2, extendUtils)

                if ok1 and ok2:
                    self.iface.mapCanvas().refresh()
                    self.messageBarUtils.showMessage(
                        FilletDigitizingMode.MESSAGE_HEADER, "Success",
                        QgsMessageBar.INFO, 5)
                    self.layer.endEditCommand()
                else:
                    self.messageBarUtils.showMessage(
                        FilletDigitizingMode.MESSAGE_HEADER,
                        "Cannot fillet segments: Amibiguous extension",
                        QgsMessageBar.WARNING, 5)
                    self.layer.destroyEditCommand()

            else:

                (pa, p1, p2, revert1, revert2) = self.computeArcParameters(
                    ip, self.segment1, self.segment2, filletRadius,
                    extendUtils)

                if pa == None:
                    # radius is too big
                    self.messageBarUtils.showMessage(
                        FilletDigitizingMode.MESSAGE_HEADER,
                        "A fillet cannot be created with those arcs.",
                        QgsMessageBar.WARNING,
                        duration=5)
                    return

                settings = QSettings()
                method = settings.value(constants.ARC_METHOD, "")
                if method == constants.ARC_METHOD_NUMBEROFPOINTS:
                    value = settings.value(constants.ARC_NUMBEROFPOINTS,
                                           0,
                                           type=int)
                else:
                    value = settings.value(constants.ARC_ANGLE, 0, type=float)

                # g is still in CRS of map
                g = CircularArc.getInterpolatedArc(p1, pa, p2, method, value)
                arcVertices = g.asPolyline()

                if len(arcVertices) >= 2:

                    projArcVertices = []
                    for vertex in arcVertices:
                        projArcVertices.append(crsTransform.transform(vertex))

                    outFeat = QgsFeature()

                    fields = self.layer.dataProvider().fields()
                    outFeat.setAttributes([None] * fields.count())
                    outFeat.setGeometry(
                        QgsGeometry.fromPolyline(projArcVertices))
                    self.layer.addFeature(outFeat)

                    # trim segments
                    p1 = crsTransform.transform(p1)
                    feature1 = self.getFeature(self.segment1)
                    geom1 = feature1.geometry()
                    geom1.insertVertex(p1.x(), p1.y(),
                                       self.segment1.afterVertexNr)
                    if revert1:
                        geom1.deleteVertex(self.segment1.afterVertexNr +
                                           1)  # insertion above => +1
                    else:
                        geom1.deleteVertex(self.segment1.beforeVertexNr)
                    self.layer.changeGeometry(feature1.id(), geom1)

                    p2 = crsTransform.transform(p2)
                    feature2 = self.getFeature(self.segment2)
                    geom2 = feature2.geometry()
                    geom2.insertVertex(p2.x(), p2.y(),
                                       self.segment2.afterVertexNr)
                    if revert2:
                        geom2.deleteVertex(self.segment2.afterVertexNr +
                                           1)  # insertion above => +1
                    else:
                        geom2.deleteVertex(self.segment2.beforeVertexNr)
                    self.layer.changeGeometry(feature2.id(), geom2)

                    self.iface.mapCanvas().refresh()
                    self.messageBarUtils.showMessage(
                        FilletDigitizingMode.MESSAGE_HEADER, "Success",
                        QgsMessageBar.INFO, 5)

                    self.layer.endEditCommand()

                else:
                    self.messageBarUtils.showMessage(
                        FilletDigitizingMode.MESSAGE_HEADER,
                        "No arc was created",
                        QgsMessageBar.WARNING,
                        duration=5)
                    self.layer.destroyEditCommand()

        except Exception as e:
            QgsMessageLog.logMessage(repr(e))
            self.messageBarUtils.removeAllMessages()
            self.messageBarUtils.showMessage(
                FilletDigitizingMode.MESSAGE_HEADER,
                "There was an error performing this command. See QGIS Message log for details.",
                QgsMessageBar.CRITICAL,
                duration=5)
            self.layer.destroyEditCommand()

            return
        finally:
            # select another fillet
            self.resetFillet()
            self.next()

    def extend(self, ip, snapSegment, extendUtils):
        featureExtend = self.getFeature(snapSegment)
        geometryExtend = featureExtend.geometry()
        bvExtend = geometryExtend.vertexAt(snapSegment.beforeVertexNr)
        avExtend = geometryExtend.vertexAt(snapSegment.afterVertexNr)

        number = extendUtils.vertexIndexToMove(bvExtend, avExtend, snapSegment,
                                               ip)
        if number == None:
            # not an extend
            return False
        else:
            fid = featureExtend.id()
            geometryExtend.moveVertex(ip.x(), ip.y(), number)
            self.layer.changeGeometry(fid, geometryExtend)
            return True

    def computeArcParameters(self, ip, snapSegment1, snapSegment2,
                             filletRadius, extendUtils):

        bvExtend1 = snapSegment1.beforeVertex
        avExtend1 = snapSegment1.afterVertex
        # TODO => mustExtend =True and check the 2 segment have the same endpoint at ip (with some tolerance)
        iVToMove1 = extendUtils.vertexIndexToMove(bvExtend1,
                                                  avExtend1,
                                                  snapSegment1,
                                                  ip,
                                                  mustExtend=False)

        bvExtend2 = snapSegment2.beforeVertex
        avExtend2 = snapSegment2.afterVertex
        iVToMove2 = extendUtils.vertexIndexToMove(bvExtend2,
                                                  avExtend2,
                                                  snapSegment2,
                                                  ip,
                                                  mustExtend=False)

        qDebug(repr(iVToMove1) + " " + repr(iVToMove2))

        if iVToMove1 == None or iVToMove2 == None:
            return (None, None, None, None, None)

        segment1 = [bvExtend1, avExtend1]
        segment2 = [bvExtend2, avExtend2]

        # determine intersection point (ip) and revert if needed
        revert1 = False
        revert2 = False
        if iVToMove1 == snapSegment1.beforeVertexNr:
            segment1[0] = ip
        else:
            revert1 = True
            segment1[1] = segment1[0]
            segment1[0] = ip

        if iVToMove2 == snapSegment2.beforeVertexNr:
            segment2[0] = ip
        else:
            revert2 = True
            segment2[1] = segment2[0]
            segment2[0] = ip

        lengthSegment1 = self.length(segment1)
        normedSegment1 = self.norm(segment1, lengthSegment1)
        lengthSegment2 = self.length(segment2)
        normedSegment2 = self.norm(segment2, lengthSegment2)

        midp1 = self.pointAtDist(normedSegment1, filletRadius)
        midp2 = self.pointAtDist(normedSegment2, filletRadius)

        # get point on bisector
        midp = QgsPoint((midp1.x() + midp2.x()) / 2.0,
                        (midp1.y() + midp2.y()) / 2.0)
        # get angles along lines from intersection
        ang1 = self.angleFromX(ip, midp1)
        ang2 = self.angleFromX(ip, midp2)

        # get bisector angle
        ang = self.angleFromX(ip, midp)

        # get a half of angle between segments
        bis = abs(ang2 - ang1) / 2.0
        # calculate hypotenuse (fillet draws slice of circle tangent to segments)
        hyp = filletRadius / math.sin(bis)

        # calculate another leg of a triangle
        cat = math.sqrt(hyp**2 - filletRadius**2)

        if cat > self.length(segment1) or cat > self.length(segment2):
            # radius is too big => nothing done
            return (None, None, None, None, None)

        # calculate point on arc
        pa = self.polarPoint(ip, ang, hyp - filletRadius)
        # calculate start point of arc
        p1 = self.polarPoint(ip, ang1, cat)
        # calculate end point of arc
        p2 = self.polarPoint(ip, ang2, cat)

        return (pa, p1, p2, revert1, revert2)

    def length(self, segment):
        return math.sqrt((segment[0].x() - segment[1].x())**2 +
                         (segment[0].y() - segment[1].y())**2)

    def norm(self, segment, length):
        return [
            segment[0],
            QgsPoint(
                segment[0].x() + (segment[1].x() - segment[0].x()) / length,
                segment[0].y() + (segment[1].y() - segment[0].y()) / length)
        ]

    def pointAtDist(self, normedSegment, dist):
        return QgsPoint(
            normedSegment[0].x() + dist *
            (normedSegment[1].x() - normedSegment[0].x()),
            normedSegment[0].y() + dist *
            (normedSegment[1].y() - normedSegment[0].y()))

    def polarPoint(self, basepoint, angle, distance):
        return QgsPoint(basepoint.x() + (distance * math.cos(angle)),
                        basepoint.y() + (distance * math.sin(angle)))

    def angleFromX(self, pt1, pt2):
        dx = pt2.x() - pt1.x()
        dy = pt2.y() - pt1.y()
        ang = math.atan2(dy, dx)
        if ang < 0.0:
            return ang + 2.0 * math.pi
        else:
            return ang

    def getFeature(self, snappingResult):
        fid = snappingResult.snappedAtGeometry
        feature = QgsFeature()
        fiter = self.layer.getFeatures(QgsFeatureRequest(fid))
        if fiter.nextFeature(feature):
            return feature
        return None

    def done(self):
        self.reset()
        self.iface.mapCanvas().unsetMapTool(self)

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        if e.button() == Qt.RightButton:
            if self.step == 0:
                self.done()
                return

        if self.currentMapTool:
            self.currentMapTool.canvasPressEvent(e)

    def canvasReleaseEvent(self, e):
        if self.currentMapTool:
            self.currentMapTool.canvasReleaseEvent(e)

    def canvasMoveEvent(self, e):
        if self.currentMapTool:
            self.currentMapTool.canvasMoveEvent(e)
Exemplo n.º 2
0
class ScaleDigitizingMode(QgsMapToolEmitPoint):

    MESSAGE_HEADER = "Scale"
    DEFAULT_SCALE_FACTOR = 2.0

    def __init__(self, iface, digitizingTools):
        self.iface = iface
        self.digitizingTools = digitizingTools
        QgsMapToolEmitPoint.__init__(self, self.iface.mapCanvas())

        self.messageBarUtils = MessageBarUtils(iface)

        self.rubberBandSnap = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandSnap.setColor(QColor(255, 51, 153))
        self.rubberBandSnap.setIcon(QgsRubberBand.ICON_CROSS)
        self.rubberBandSnap.setIconSize(12)
        self.rubberBandSnap.setWidth(3)

        # we need a snapper, so we use the MapCanvas snapper
        self.snapper = QgsMapCanvasSnapper(self.iface.mapCanvas())

        self.layer = None
        self.reset()

    def setLayer(self, layer):
        self.layer = layer

    def reset(self, clearMessages=True):
        self.step = 0

        self.rubberBandSnap.reset(QGis.Point)

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

    def deactivate(self):
        self.reset()
        super(QgsMapToolEmitPoint, self).deactivate()

    def next(self):
        if self.step == 0:
            scaleFactor, found = QgsProject.instance().readEntry(
                constants.SETTINGS_KEY, constants.SETTINGS_SCALE_FACTOR, None)
            if not found:
                scaleFactor = str(ScaleDigitizingMode.DEFAULT_SCALE_FACTOR)

            _, lineEdit = self.messageBarUtils.showLineEdit(
                ScaleDigitizingMode.MESSAGE_HEADER,
                "Draw base point and set scale:", scaleFactor)
            self.lineEditScaleFactor = lineEdit
        elif self.step == 1:
            self.scaleFactor = self.lineEditScaleFactor.text()

            if self.layer.selectedFeatureCount() == 0:
                self.messageBarUtils.showYesCancel(
                    ScaleDigitizingMode.MESSAGE_HEADER,
                    "No feature selected in Layer %s. Proceed on full layer?" %
                    self.layer.name(), QgsMessageBar.WARNING, self.doScale)
                return

            self.doScale()

    def doScale(self):
        _, progress = self.messageBarUtils.showProgress(
            ScaleDigitizingMode.MESSAGE_HEADER, "Running...",
            QgsMessageBar.INFO)

        try:
            # get the actual vertices from the geometry instead of the snapping
            crsDest = self.layer.crs()
            canvas = self.iface.mapCanvas()
            mapRenderer = canvas.mapSettings()
            crsSrc = mapRenderer.destinationCrs()
            crsTransform = QgsCoordinateTransform(crsSrc, crsDest)
            # arcnode is still in map canvas coordinate => transform to layer coordinate
            self.basePoint = crsTransform.transform(self.basePoint)

            try:
                scaleFactor = float(self.scaleFactor)
                # save for next time
                QgsProject.instance().writeEntry(
                    constants.SETTINGS_KEY, constants.SETTINGS_SCALE_FACTOR,
                    scaleFactor)
            except ValueError:
                scaleFactor = ScaleDigitizingMode.DEFAULT_SCALE_FACTOR

            self.iface.mapCanvas().freeze(True)

            outFeat = QgsFeature()
            inGeom = QgsGeometry()

            current = 0
            features = vectorlayerutils.features(self.layer)
            total = 100.0 / float(len(features))
            for f in features:
                inGeom = f.geometry()
                attrs = f.attributes()

                # break into multiple geometries if needed
                if inGeom.isMultipart():
                    geometries = inGeom.asMultiPolyline()
                    outGeom = []

                    for g in geometries:
                        polyline = self.scalePolyline(g, scaleFactor)
                        outGeom.append(polyline)

                    outGeom = QgsGeometry.fromMultiPolyline(outGeom)

                else:
                    polyline = self.scalePolyline(inGeom.asPolyline(),
                                                  scaleFactor)
                    outGeom = QgsGeometry.fromPolyline(polyline)

                outFeat.setAttributes(attrs)
                outFeat.setGeometry(outGeom)
                self.layer.addFeature(outFeat)

                self.layer.deleteFeature(f.id())

                current += 1
                progress.setValue(int(current * total))

        except Exception as e:
            QgsMessageLog.logMessage(repr(e))
            self.messageBarUtils.showMessage(
                ScaleDigitizingMode.MESSAGE_HEADER,
                "There was an error performing this command. See QGIS Message log for details.",
                QgsMessageBar.CRITICAL,
                duration=5)
            self.layer.destroyEditCommand()

            return
        finally:
            self.iface.mapCanvas().freeze(False)
            self.iface.mapCanvas().refresh()

        # if here: Success!
        self.messageBarUtils.showMessage(ScaleDigitizingMode.MESSAGE_HEADER,
                                         "Success", QgsMessageBar.INFO, 5)

        self.layer.endEditCommand()

        self.iface.mapCanvas().unsetMapTool(self)

    def scalePolyline(self, polyline, scaleFactor):
        for vertex in polyline:
            # scale
            relBP = QgsPoint(vertex.x() - self.basePoint.x(),
                             vertex.y() - self.basePoint.y())
            vertex.setX(self.basePoint.x() + scaleFactor * relBP.x())
            vertex.setY(self.basePoint.y() + scaleFactor * relBP.y())

        return polyline

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        pass

    def canvasReleaseEvent(self, e):
        pos = QPoint(e.pos().x(), e.pos().y())
        result = self.snap(pos)
        if result != []:
            self.basePoint = result[0].snappedVertex
        else:
            self.basePoint = self.toMapCoordinates(e.pos())

        self.step = 1
        self.next()

    def canvasMoveEvent(self, e):
        pos = QPoint(e.pos().x(), e.pos().y())
        result = self.snap(pos)
        self.rubberBandSnap.reset(QGis.Point)
        if result != []:
            self.rubberBandSnap.addPoint(result[0].snappedVertex, True)

    def snap(self, pos):
        if self.digitizingTools.isBackgroundSnapping:
            (_, result) = self.snapper.snapToBackgroundLayers(pos)
        else:
            (_,
             result) = self.snapper.snapToCurrentLayer(pos,
                                                       QgsSnapper.SnapToVertex)
        return result