Esempio n. 1
0
class CenterlineAdvancedDigitizingMode(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        QgsMapToolEmitPoint.__init__(self, self.iface.mapCanvas())

        self.messageBarUtils = MessageBarUtils(iface)
        self.segmentFinderTool = SegmentFinderTool(self.iface.mapCanvas())

        # just needs to rubberbands (when selecting 2nd segment, replaced by selectedLine or
        # hidden)
        self.rubberBandSelectedSegment = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandSelectedSegment.setColor(QColor(255, 0, 0))
        self.rubberBandSelectedSegment.setWidth(2)

        self.rubberBandSelectedLine = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandSelectedLine.setColor(QColor(255, 0, 0))
        self.rubberBandSelectedLine.setWidth(2)

        self.defaultIndex = None
        self.layer = None
        self.reset()

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

    def reset(self, clearMessages=True):
        self.step = 0
        self.snappingResults = []
        self.snappingPointsClicked = []
        self.subPolylines = []
        self.defaultIndex = None
        self.orderSelection = False

        self.rubberBandSelectedSegment.reset(QGis.Line)
        self.rubberBandSelectedLine.reset(QGis.Line)

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

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

    def resetCenterline(self):
        self.step = 0
        self.snappingResults = []
        self.snappingPointsClicked = []
        self.subPolylines = []
        self.rubberBandSelectedSegment.reset(QGis.Line)
        self.rubberBandSelectedLine.reset(QGis.Line)

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

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

    def next(self):
        if self.step == 0:
            self.messageBarUtils.showButton(
                "Centerline",
                "Select starting segment of first line",
                "Done",
                buttonCallback=self.done)
            _, candidateLayers, _ = self.listCandidateLayers(
                onlyEditable=False)
            self.segmentFinderTool.layers = candidateLayers
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.edgeFound)
        elif self.step == 1:
            self.messageBarUtils.showMessage(
                "Centerline", "Select end segment of first line", duration=0)
            _, candidateLayers, _ = self.listCandidateLayers(
                onlyEditable=False)
            self.segmentFinderTool.layers = candidateLayers
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.edgeFound)
        elif self.step == 2:
            self.messageBarUtils.showMessage(
                "Centerline",
                "Select starting segment of second line",
                duration=0)
            _, candidateLayers, _ = self.listCandidateLayers(
                onlyEditable=False)
            self.segmentFinderTool.layers = candidateLayers
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.edgeFound)
        elif self.step == 3:
            candidates, candidateLayers, defaultIndex = self.listCandidateLayers(
                onlyEditable=False)
            if self.defaultIndex <> None and defaultIndex < len(
                    candidateLayers):
                defaultIndex = self.defaultIndex
            _, combobox = self.messageBarUtils.showCombobox(
                "Centerline", "Select end segment of second line", candidates,
                defaultIndex)
            self.segmentFinderTool.layers = candidateLayers
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(
                partial(self.lastEdgeFound, candidateLayers, combobox))
        elif self.step == 4:
            self.doCenterline()

    def edgeFound(self, result, pointClicked):

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

        self.segmentFinderTool.deactivate()

        if self.step == 0 or self.step == 2:
            self.snappingResults.append(result)
            self.snappingPointsClicked.append(pointClicked)

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

            self.step += 1
        else:
            # step 1 or 3
            p = self._getSubPolyline(self.snappingResults[-1], result,
                                     self.snappingPointsClicked[0])
            if p:
                self.subPolylines.append(p)

                self.snappingResults.append(result)
                self.snappingPointsClicked.append(pointClicked)

                self.rubberBandSelectedSegment.reset(QGis.Line)
                self.rubberBandSelectedLine.reset(QGis.Line)

                if self.step == 1:
                    # don't draw when step is 3
                    polyline = QgsGeometry.fromPolyline(p)
                    self.rubberBandSelectedLine.setToGeometry(
                        polyline, self.snappingResults[0].layer)
                    self.rubberBandSelectedLine.show()

                self.step += 1
            else:
                # not valid choice for second segment: not on same layer or feature or subpolyline (if multipolyline)
                self.messageBarUtils.showMessage(
                    "Centerline",
                    "Invalid choice for second segment: Select a segment in the same part of the same geometry as the first",
                    QgsMessageBar.WARNING,
                    duration=5)

        self.next()

    def lastEdgeFound(self, candidateLayers, combobox, result, pointClicked):
        try:
            index = combobox.currentIndex()
            self.defaultIndex = index
            self.outputLayer = candidateLayers[index]
        except:
            # can happen in message with comobobox is closed by the user
            self.outputLayer = candidateLayers[self.defaultIndex]

        self.edgeFound(result, pointClicked)

    def listCandidateLayers(self, onlyEditable=True):
        candidates = []
        candidateLayers = []
        defaultIndex = 0
        for layer in QgsMapLayerRegistry.instance().mapLayers().values():
            if (layer.type() == QgsMapLayer.VectorLayer
                    and layer.geometryType() == QGis.Line
                    and (not onlyEditable or layer.isEditable())):
                candidates.append(layer.name())
                candidateLayers.append(layer)
        sortedCandidates = sorted(enumerate(candidates), key=itemgetter(1))
        candidates = map(itemgetter(1), sortedCandidates)
        candidateLayers = map(lambda x: candidateLayers[x[0]],
                              sortedCandidates)
        for index in range(len(candidateLayers)):
            if candidateLayers[index] == self.layer:
                defaultIndex = index
        return candidates, candidateLayers, defaultIndex

    def doCenterline(self):
        self.messageBarUtils.showMessage("Centerline",
                                         "Running...",
                                         duration=0)
        try:
            qDebug("New Centerline Advanced")

            p1 = self.subPolylines[0]
            p2 = self.subPolylines[1]

            if self._mustReverse(p1, p2):
                # reverse the node order of any of the 2
                p1 = list(reversed(p1))

            # both input geometries and output layer are in same CRS
            if len(p1) == len(p2):
                p = self._processSimple(p1, p2)
            else:
                # voronoi with post processing
                p = self._processVoronoiPolygons(p1, p2)

            if p:
                self.outputLayer.beginEditCommand("Centerline")

                fieldCount = self.outputLayer.dataProvider().fields().count()
                feature = QgsFeature()
                feature.initAttributes(fieldCount)
                feature.setGeometry(p)
                self.outputLayer.addFeature(feature)

                self.outputLayer.endEditCommand()
                self.iface.mapCanvas().refresh()

                self.messageBarUtils.showMessage(
                    "Centerline",
                    "The centerline was created successfully",
                    duration=2)
            else:
                self.messageBarUtils.showMessage("Centerline",
                                                 "No centerline was created",
                                                 QgsMessageBar.WARNING,
                                                 duration=2)

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

        self.resetCenterline()
        self.next()

    def _processSimple(self, p1, p2):
        #just iterate
        p = []
        for i in range(len(p1)):
            endSegment = [p1[i], p2[i]]
            # middle of each end segments
            centerlineEndPoint = QgsPoint(
                (endSegment[0].x() + endSegment[1].x()) / 2,
                (endSegment[0].y() + endSegment[1].y()) / 2)
            p.append(centerlineEndPoint)
        return QgsGeometry.fromPolyline(p)

    def _processVoronoiPolygons(self, p1, p2):
        tempDir = None
        try:
            # define a polygon formed by p1 and p2: add edge at both ends to
            # close it. P1 and p2 have already been ordered to face each other
            # in the same direction
            ring = p1[0:]
            ring.extend([p1[-1], p2[-1]])
            ring.extend(p2[-1::-1])
            ring.extend([p2[0], p1[0]])
            pipePolygon = QgsGeometry.fromPolygon([ring])
            end1 = QgsGeometry.fromPolyline([p1[-1], p2[-1]])
            end2 = QgsGeometry.fromPolyline([p2[0], p1[0]])

            # create temp shp
            tempDir = tempfile.mkdtemp()
            vl = QgsVectorLayer(
                "LineString?crs=%s" % self.outputLayer.crs().toWkt(), "temp",
                "memory")
            pr = vl.dataProvider()
            vl.startEditing()
            pr.addAttributes([QgsField("id", QVariant.Int, "Integer")])
            vl.commitChanges()

            vl.startEditing()
            for p in [p1, p2]:
                feature = QgsFeature()
                feature.initAttributes(1)
                feature.setGeometry(QgsGeometry.fromPolyline(p))
                vl.addFeature(feature)
            vl.commitChanges()

            qDebug("mem " + repr(vl.featureCount()))

            inputShp = os.path.join(tempDir, "input.shp")
            QgsVectorFileWriter.writeAsVectorFormat(vl, inputShp, "UTF-8",
                                                    vl.crs())

            #             vl=QgsVectorLayer("Polygon?crs=%s" % self.outputLayer.crs().toWkt(), "temp3", "memory")
            #             pr = vl.dataProvider()
            #             vl.startEditing()
            #             pr.addAttributes([QgsField("id",QVariant.Int,"Integer")])
            #             vl.commitChanges()
            #             vl.startEditing()
            #             feature=QgsFeature()
            #             feature.initAttributes(1)
            #             feature.setGeometry(pipePolygon)
            #             vl.addFeature(feature)
            #             vl.commitChanges()
            # QgsMapLayerRegistry.instance().addMapLayer(vl)

            # processing often fails randomly
            # try multiple times
            trials = 0
            success = False
            while trials < 3 and not success:
                try:
                    output = processing.runalg("qgis:densifygeometries",
                                               inputShp, 100, None)
                    output = processing.runalg("qgis:extractnodes",
                                               output["OUTPUT"], None)
                    output = processing.runalg("qgis:voronoipolygons",
                                               output["OUTPUT"], 0.001, None)
                    voronoi = QgsVectorLayer(output["OUTPUT"], "voronoi",
                                             "ogr")
                    #                     QgsMapLayerRegistry.instance().addMapLayer(voronoi, addToLegend=True)
                    #                     voronoi.setLayerTransparency(50)

                    output = processing.runalg("qgis:extractnodes", inputShp,
                                               None)
                    originalNodes = QgsVectorLayer(output["OUTPUT"],
                                                   "original", "ogr")

                    success = originalNodes.featureCount > 0 and voronoi.featureCount(
                    ) > 0
                except:
                    trials += 1

            if not success:
                return None

            qDebug("after processing")

            # 1. create a spatial index with all the voronoi polygons
            # 2. Get the lsit of edges from the vornoi polygons that are in the pipePolygon
            voronoiEdges = []
            voronoiEdgeInsideSet = set()
            spatialIndex = QgsSpatialIndex()
            for f in voronoi.getFeatures():
                spatialIndex.insertFeature(f)
                geometry = f.geometry()

                # voronoi polygons are single parts with one ring
                p = geometry.asPolygon()
                for edgeP in self._extractEdgesFromPolyline(p[0]):
                    edge = QgsGeometry.fromPolyline(edgeP)
                    if edge.within(pipePolygon):
                        key = self._keyFromEdge(edgeP)
                        if key not in voronoiEdgeInsideSet:
                            # prevent duplicates (voronoi polygons share a border)
                            voronoiEdges.append(edgeP)
                            voronoiEdgeInsideSet.add(key)

            voronoiPolygonMajorEdges = []
            qDebug(repr(originalNodes.featureCount()))
            for originalNode in originalNodes.getFeatures():
                geometry = originalNode.geometry()
                candidates = spatialIndex.intersects(geometry.boundingBox())
                if len(candidates) > 0:
                    for candidate in candidates:
                        fCandidate = voronoi.getFeatures(
                            QgsFeatureRequest(candidate)).next()
                        gCandidate = fCandidate.geometry()
                        if geometry.within(gCandidate):
                            p = gCandidate.asPolygon()
                            edges = self._extractEdgesFromPolyline(p[0])
                            for edge in edges:
                                key = self._keyFromEdge(edge)
                                if key not in voronoiEdgeInsideSet:
                                    # do not add the edge if already in the centerline
                                    #gEdge=QgsGeometry.fromPolyline(edge)
                                    #if gEdge.intersects(pipePolygon):
                                    voronoiPolygonMajorEdges.append(key)
                            break

            qDebug("after spatindex")

            # recursively combine voronoiedges into one big polyline
            voronoiEdges = QgsGeometry.fromMultiPolyline(voronoiEdges)
            centerlinePolyline = voronoiEdges.combine(
                voronoiEdges)  #self.union(voronoiEdges)

            if centerlinePolyline.isMultipart():
                lines = centerlinePolyline.asMultiPolyline()
            else:
                lines = [centerlinePolyline.asPolyline()]

            qDebug("after merge")

            # key template
            kT = "%3.11f"

            nodeToVertexIndex = {}
            nodeCount = {}
            for i in range(len(lines)):
                line = lines[i]
                for j in range(len(line)):
                    point = line[j]
                    key = (kT % point.x(), kT % point.y())
                    count = nodeCount.get(key, 0)
                    if j <> 0 and j <> len(line) - 1:
                        # each of those nodes is linked to 2 segments
                        count += 2
                    else:
                        count += 1
                    nodeCount[key] = count

                    nodeToVertexIndex[key] = (i, j)


#             for i in range(len(lines)):
#                 line=lines[i]
#                 for j in range(len(line)):
#                     point=line[j]
#                     key=(kT%point.x(),kT%point.y())
#                     qDebug(repr(key)+" "+repr(nodeCount[key]))

            for edgeKey in voronoiPolygonMajorEdges:
                p1x, p1y, p2x, p2y = edgeKey
                for key in [(kT % p1x, kT % p1y), (kT % p2x, kT % p2y)]:
                    if key == ('-76.87093621506', '42.15309787572'):
                        qDebug("found mine " + repr(nodeCount.get(key, 0)))
                    count = nodeCount.get(key, 0)
                    if count == 2:
                        # only counts if in the main segment (not the branches)
                        nodeCount[key] = count + 1

            qDebug("after indexing")

            # cleanup: only keep important vertices
            toKeep = set(filter(lambda x: nodeCount[x] > 2, nodeCount.keys()))
            toRemove = set(nodeToVertexIndex.keys()) - toKeep
            toRemoveIndices = map(lambda x: nodeToVertexIndex[x], toRemove)
            for lineIndex, pointIndex in sorted(toRemoveIndices,
                                                cmp=self._compareIndices,
                                                reverse=True):
                line = lines[lineIndex]
                del line[pointIndex]

            self._cleanupDegenerateSegments(lines)

            qDebug("after cleanup")

            return self.union(map(lambda x: QgsGeometry.fromPolyline(x),
                                  lines))
        finally:
            if tempDir:
                pass  #shutil.rmtree(tempDir, True)
            qDebug("after union")

    def union(self, lines):
        return reduce(lambda m, x: m.combine(x), lines[1:], lines[0])

    def _compareIndices(self, x, y):
        i1, j1 = x
        i2, j2 = y
        if i1 < i2:
            return -1
        elif i2 < i1:
            return 1
        else:
            if j1 < j2:
                return -1
            elif j2 < j1:
                return 1
            else:
                return 0

    def _keyFromEdge(self, edge):
        return (edge[0].x(), edge[0].y(), edge[1].x(), edge[1].y())

    def _cleanupDegenerateSegments(self, lines):
        linesToRemove = []
        for i in range(len(lines)):
            line = lines[i]
            if len(line) <= 1:
                linesToRemove.append(i)

        for i in reversed(linesToRemove):
            del lines[i]

        return lines

    def _extractEdgesFromPolyline(self, p):
        edges = []
        for i in range(len(p) - 1):
            edgeP = p[i:i + 2]
            edges.append(edgeP)
        return edges

    def _mustReverse(self, p1, p2):
        polyline1 = QgsGeometry.fromPolyline(p1)
        polyline2 = QgsGeometry.fromPolyline(p2)

        candidates1 = (QgsGeometry.fromPolyline([p1[0], p2[0]]),
                       QgsGeometry.fromPolyline([p1[-1], p2[-1]]))

        return ((candidates1[0].intersects(candidates1[1])
                 and not candidates1[0].equals(candidates1[1]))
                or polyline1.crosses(candidates1[0])
                or polyline1.crosses(candidates1[1])
                or polyline2.crosses(candidates1[0])
                or polyline2.crosses(candidates1[1]))

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

    def _getSegment(self, snappingResult):
        feature = self._getFeature(snappingResult)
        geometry = feature.geometry()
        bv = geometry.vertexAt(snappingResult.beforeVertexNr)
        av = geometry.vertexAt(snappingResult.afterVertexNr)
        return QgsGeometry.fromPolyline([bv, av])

    def _getSubPolyline(self, snappingResult1, snappingResult2, pointClicked1):
        """
        computes the polyline defined by the 2 segments: all the segments between those 2
        will be included in the returned polyline. If Ctrl held while selecting the 2nd segment, 
        The side of the first segment the user clicked on determines which direction to go to add those segments
        """
        if (snappingResult1.layer.id() <> snappingResult2.layer.id()
                or snappingResult1.snappedAtGeometry <>
                snappingResult2.snappedAtGeometry):
            return None

        feature = self._getFeature(snappingResult1)
        geometry = feature.geometry()

        if geometry.isMultipart():
            mp = geometry.asMultipolyline()
            p, relative = vectorlayerutils.polylineWithVertexAtIndex(
                mp, snappingResult1.beforeVertexNr)
            _, relative2 = vectorlayerutils.polylineWithVertexAtIndex(
                mp, snappingResult2.beforeVertexNr)
            if relative <> relative2:
                # not on the same sub polyline
                return None
        else:
            p = geometry.asPolyline()
            relative = 0

        if self.orderSelection:
            bv = geometry.vertexAt(snappingResult1.beforeVertexNr)
            av = geometry.vertexAt(snappingResult1.afterVertexNr)

            # get the direction in which to add segments
            # vertex closest to point clicked is the top vertex: direction will be from
            # the other vertex to that one
            extendUtils = ExtendUtils(self.iface)
            vertexNrDirection = extendUtils.vertexIndexToMove(bv,
                                                              av,
                                                              snappingResult1,
                                                              pointClicked1,
                                                              mustExtend=False)

            # positive if index of vertices is incremented
            direction = vertexNrDirection == snappingResult1.afterVertexNr

            if not direction:
                # swap
                temp = snappingResult2
                snappingResult2 = snappingResult1
                snappingResult1 = temp

            if snappingResult2.afterVertexNr <= snappingResult1.beforeVertexNr:
                # wrap around
                nodes = p[snappingResult1.beforeVertexNr - relative:]
                nodes.extend(p[0:snappingResult2.afterVertexNr - relative + 1])
            else:
                nodes = p[snappingResult1.beforeVertexNr -
                          relative:snappingResult2.afterVertexNr - relative +
                          1]
        else:
            if snappingResult2.beforeVertexNr < snappingResult1.beforeVertexNr:
                # swap
                temp = snappingResult2
                snappingResult2 = snappingResult1
                snappingResult1 = temp
                direction = False
            else:
                direction = True

            nodes = p[snappingResult1.beforeVertexNr -
                      relative:snappingResult2.afterVertexNr - relative + 1]

        qDebug(repr(nodes))

        return nodes if direction else list(reversed(nodes))

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

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        # use right button instead of clicking on button in message bar
        if e.button() == Qt.RightButton:
            if self.step == 0:
                self.done()
                return

        self.orderSelection = e.modifiers() == Qt.ControlModifier
        qDebug(repr(self.orderSelection))

        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)

    def snap(self, pos):
        (_, result) = self.snapper.snapToBackgroundLayers(pos)
        return result
Esempio n. 2
0
class CenterlineDigitizingMode(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        QgsMapToolEmitPoint.__init__(self, self.iface.mapCanvas())

        self.messageBarUtils = MessageBarUtils(iface)

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

        self.defaultIndex = None
        self.layer = None
        self.reset()

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

    def reset(self, clearMessages=True):
        self.step = 0
        self.snappingResultFirstEdge = None
        self.snappingResultSecondEdge = None
        self.defaultIndex = None

        self.rubberBandSelectedSegment.reset(QGis.Line)

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

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

    def resetCenterline(self):
        self.step = 0
        self.snappingResultFirstEdge = None
        self.snappingResultSecondEdge = None
        self.rubberBandSelectedSegment.reset(QGis.Line)
        self.segmentFinderTool.layers = None

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

    def next(self):
        if self.step == 0:
            self.messageBarUtils.showButton("Centerline",
                                            "Select first edge",
                                            "Done",
                                            buttonCallback=self.done)
            _, candidateLayers, _ = self.listCandidateLayers(
                onlyEditable=False)
            self.segmentFinderTool.layers = candidateLayers
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.firstEdgeFound)
        elif self.step == 1:
            candidates, candidateLayers, defaultIndex = self.listCandidateLayers(
            )
            if self.defaultIndex <> None and defaultIndex < len(
                    candidateLayers):
                defaultIndex = self.defaultIndex
            _, combobox = self.messageBarUtils.showCombobox(
                "Centerline", "Select second edge", candidates, defaultIndex)
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(
                partial(self.secondEdgeFound, candidateLayers, combobox))
        elif self.step == 2:
            self.doCenterline()

    def firstEdgeFound(self, result, pointClicked):
        self.snappingResultFirstEdge = result

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

        self.segmentFinderTool.deactivate()

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

        self.step = 1
        self.next()

    def secondEdgeFound(self, candidateLayers, combobox, result, pointClicked):
        index = combobox.currentIndex()
        self.defaultIndex = index
        self.outputLayer = candidateLayers[index]

        self.snappingResultSecondEdge = result

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

        self.segmentFinderTool.deactivate()

        self.step = 2
        self.next()

    def listCandidateLayers(self, onlyEditable=True):
        candidates = []
        candidateLayers = []
        defaultIndex = 0
        for layer in QgsMapLayerRegistry.instance().mapLayers().values():
            if (layer.type() == QgsMapLayer.VectorLayer
                    and layer.geometryType() == QGis.Line
                    and (not onlyEditable or layer.isEditable())):
                candidates.append(layer.name())
                candidateLayers.append(layer)
        sortedCandidates = sorted(enumerate(candidates), key=itemgetter(1))
        candidates = map(itemgetter(1), sortedCandidates)
        candidateLayers = map(lambda x: candidateLayers[x[0]],
                              sortedCandidates)
        for index in range(len(candidateLayers)):
            if candidateLayers[index] == self.layer:
                defaultIndex = index
        return candidates, candidateLayers, defaultIndex

    def doCenterline(self):
        self.messageBarUtils.showMessage("Centerline",
                                         "Running...",
                                         duration=0)
        try:
            qDebug("New Centerline")

            firstSegment = self._getSegment(self.snappingResultFirstEdge)
            secondSegment = self._getSegment(self.snappingResultSecondEdge)
            endSegments = self._getEndSegments(firstSegment, secondSegment)

            # middle of each end segments
            centerlineEndPoint1 = QgsPoint(
                (endSegments[0].vertexAt(0).x() +
                 endSegments[0].vertexAt(1).x()) / 2,
                (endSegments[0].vertexAt(0).y() +
                 endSegments[0].vertexAt(1).y()) / 2)
            centerlineEndPoint2 = QgsPoint(
                (endSegments[1].vertexAt(0).x() +
                 endSegments[1].vertexAt(1).x()) / 2,
                (endSegments[1].vertexAt(0).y() +
                 endSegments[1].vertexAt(1).y()) / 2)
            line = QgsGeometry.fromPolyline(
                [centerlineEndPoint1, centerlineEndPoint2])
            self.outputLayer.beginEditCommand("Centerline")
            feature = QgsFeature()
            feature.initAttributes(
                self.outputLayer.dataProvider().fields().count())
            feature.setGeometry(line)
            self.outputLayer.addFeature(feature)

            self.outputLayer.endEditCommand()

            self.iface.mapCanvas().refresh()

            self.messageBarUtils.showMessage(
                "Centerline",
                "The centerline was created successfully",
                duration=2)
        except Exception as e:
            QgsMessageLog.logMessage(repr(e))
            self.messageBarUtils.showMessage(
                "Centerline",
                "There was an error performing this command. See QGIS Message log for details",
                QgsMessageBar.CRITICAL,
                duration=5)
            self.outputLayer.destroyEditCommand()
            self.done()

        self.resetCenterline()
        self.next()

    def _getEndSegments(self, firstSegment, secondSegment):
        candidates1 = (QgsGeometry.fromPolyline(
            [firstSegment.vertexAt(0),
             secondSegment.vertexAt(0)]),
                       QgsGeometry.fromPolyline([
                           firstSegment.vertexAt(1),
                           secondSegment.vertexAt(1)
                       ]))
        candidates2 = (QgsGeometry.fromPolyline(
            [firstSegment.vertexAt(0),
             secondSegment.vertexAt(1)]),
                       QgsGeometry.fromPolyline([
                           firstSegment.vertexAt(1),
                           secondSegment.vertexAt(0)
                       ]))
        if candidates1[0].intersects(candidates1[1]):
            return candidates2
        else:
            return candidates1

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

    def _getSegment(self, snappingResult):
        feature = self._getFeature(snappingResult)
        geometry = feature.geometry()
        bv = geometry.vertexAt(snappingResult.beforeVertexNr)
        av = geometry.vertexAt(snappingResult.afterVertexNr)
        return QgsGeometry.fromPolyline([bv, av])

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

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        # use right button instead of clicking on button in message bar
        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)
Esempio n. 3
0
class CopyDigitizingMode(QgsMapToolEmitPoint):

    MESSAGE_HEADER = "Copy"

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

        self.messageBarUtils = MessageBarUtils(iface)

        self.rubberBandMoveAxis = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandMoveAxis.setColor(QColor(255, 0, 0))
        self.rubberBandMoveAxis.setWidth(2)

        self.rubberBandGeometriesPreview = QgsRubberBand(
            self.iface.mapCanvas())
        self.rubberBandGeometriesPreview.setColor(QColor(0, 255, 0))
        self.rubberBandGeometriesPreview.setWidth(1)

        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)

        self.isEmittingPoint = False

        # 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

        crsDest = self.layer.crs()
        canvas = self.iface.mapCanvas()
        mapRenderer = canvas.mapSettings()
        crsSrc = mapRenderer.destinationCrs()
        self.crsTransform = QgsCoordinateTransform(crsSrc, crsDest)
        # cannot indicate direction in QgsGeometry.transform
        self.crsTransformReverse = QgsCoordinateTransform(crsDest, crsSrc)

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

        self.rubberBandMoveAxis.reset(QGis.Line)
        self.rubberBandGeometriesPreview.reset(QGis.Line)
        self.rubberBandSnap.reset(QGis.Point)

        settings = QSettings()
        self.isPreviewEnabled = settings.value(constants.PREVIEW_ENABLED,
                                               type=bool)
        self.isLimitPreviewEnabled = settings.value(
            constants.PREVIEW_LIMIT_ENABLED, type=bool)
        self.maxLimitPreview = settings.value(constants.PREVIEW_LIMIT_MAX,
                                              type=int)

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

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

    def next(self):
        if self.step == 0:
            self.messageBarUtils.showMessage(
                CopyDigitizingMode.MESSAGE_HEADER,
                "Select base point of displacement",
                duration=0)
        elif self.step == 1:
            self.messageBarUtils.showButton(
                CopyDigitizingMode.MESSAGE_HEADER,
                "Select second point of displacement",
                "Done",
                buttonCallback=self.done)
        elif self.step == 2:
            if self.layer.selectedFeatureCount() == 0:
                self.messageBarUtils.showYesCancel(
                    CopyDigitizingMode.MESSAGE_HEADER,
                    "No feature selected in Layer %s. Proceed on full layer?" %
                    self.layer.name(), QgsMessageBar.WARNING, self.doCopy,
                    self.deactivate)
                return

            self.doCopy()

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

        try:

            self.rubberBandGeometriesPreview.reset(QGis.Line)

            self.layer.beginEditCommand(CopyDigitizingMode.MESSAGE_HEADER)

            ok = self.prepareDisplacement()

            if not ok:
                self.messageBarUtils.showMessage(
                    CopyDigitizingMode.MESSAGE_HEADER,
                    "Invalid displacement.",
                    QgsMessageBar.WARNING,
                    duration=5)
                self.layer.destroyEditCommand()
                return

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

            outFeat = QgsFeature()

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

                # perform displacement in map coordinates instead of layer coordinates
                inGeom.transform(self.crsTransformReverse)
                outGeom = self.movePolyline(inGeom)
                outGeom.transform(self.crsTransform)

                outFeat.initAttributes(len(attrs))
                for index in range(len(attrs)):
                    if index <> pgidIndex:
                        outFeat.setAttribute(index, attrs[index])
                outFeat.setGeometry(outGeom)
                self.layer.addFeature(outFeat)

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

            self.messageBarUtils.showMessage(CopyDigitizingMode.MESSAGE_HEADER,
                                             "Success", QgsMessageBar.INFO, 5)
            self.layer.endEditCommand()

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

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

        self.resetCopy()
        self.next()

    def resetCopy(self):
        self.step = 1

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

    def prepareDisplacement(self):
        # rubber bands are in crs of map canvas
        self.dx = self.endPoint.x() - self.startPoint.x()
        self.dy = self.endPoint.y() - self.startPoint.y()
        return True

    def movePolyline(self, geom):
        geom.translate(self.dx, self.dy)
        return geom

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        # use right button instead of clicking on button in message bar
        if e.button() == Qt.RightButton:
            if self.step == 1:
                self.done()
        elif e.button() == Qt.LeftButton:
            if self.step == 0:
                modifiers = e.modifiers()
                if modifiers == Qt.ControlModifier:
                    self.isDuplicate = True

                # we snap to the current layer (we don't have exclude points and use the tolerances from the qgis properties)
                pos = QPoint(e.pos().x(), e.pos().y())
                result = self.snap(pos)
                if result != []:
                    self.startPoint = QgsPoint(result[0].snappedVertex)
                else:
                    self.startPoint = self.toMapCoordinates(e.pos())

                self.endPoint = self.startPoint
                self.isEmittingPoint = True

                self.rubberBandMoveAxis.reset(QGis.Line)
                self.rubberBandMoveAxis.addPoint(self.startPoint)
                self.rubberBandMoveAxis.addPoint(self.endPoint)
                self.rubberBandMoveAxis.show()

                self.step = 1
                self.next()
            elif self.step == 1:
                pos = QPoint(e.pos().x(), e.pos().y())
                result = self.snap(pos)
                if result != []:
                    self.endPoint = QgsPoint(result[0].snappedVertex)
                else:
                    self.endPoint = self.toMapCoordinates(e.pos())

                self.step = 2
                self.next()

    def canvasReleaseEvent(self, e):
        pass

    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)

        if not self.isEmittingPoint:
            return

        if self.step == 1:
            if result != []:
                self.endPoint = QgsPoint(result[0].snappedVertex)
            else:
                self.endPoint = self.toMapCoordinates(e.pos())
            self.rubberBandMoveAxis.reset(QGis.Line)
            self.rubberBandMoveAxis.addPoint(self.startPoint)
            self.rubberBandMoveAxis.addPoint(self.endPoint)
            self.rubberBandMoveAxis.show()

            self.updateGeometriesPreview()

    def updateGeometriesPreview(self):
        ok = self.prepareDisplacement()
        self.rubberBandGeometriesPreview.reset(QGis.Line)

        if not ok:
            return

        if not self.isPreviewEnabled:
            return

        if self.isLimitPreviewEnabled:
            maxCount = self.maxLimitPreview
        else:
            maxCount = self.layer.featureCount()

        currentCount = 0
        features = vectorlayerutils.features(self.layer)
        for f in features:
            inGeom = f.geometry()
            inGeom.transform(self.crsTransformReverse)
            inGeom = self.movePolyline(inGeom)
            self.rubberBandGeometriesPreview.addGeometry(inGeom, None)

            currentCount += 1
            if currentCount >= maxCount:
                break

        self.rubberBandGeometriesPreview.show()

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

    MESSAGE_HEADER = "Copy from multiple layers"

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

        self.messageBarUtils = MessageBarUtils(iface)

        self.rubberBandMoveAxis = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandMoveAxis.setColor(QColor(255, 0, 0))
        self.rubberBandMoveAxis.setWidth(2)

        self.rubberBandGeometriesPreview = QgsRubberBand(
            self.iface.mapCanvas())
        self.rubberBandGeometriesPreview.setColor(QColor(0, 255, 0))
        self.rubberBandGeometriesPreview.setWidth(1)

        self.rubberBandPointsPreview = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandPointsPreview.setColor(QColor(0, 255, 0))
        self.rubberBandPointsPreview.setWidth(2)
        self.rubberBandPointsPreview.setIcon(QgsRubberBand.ICON_CIRCLE)

        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)

        self.isEmittingPoint = False

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

        self.reset()

    def setLayer(self, layer):
        # this mode ignores the currently selected layer
        pass

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

        self.rubberBandMoveAxis.reset(QGis.Line)
        self.rubberBandGeometriesPreview.reset(QGis.Line)
        self.rubberBandPointsPreview.reset(QGis.Point)
        self.rubberBandSnap.reset(QGis.Point)

        settings = QSettings()
        self.isPreviewEnabled = settings.value(constants.PREVIEW_ENABLED,
                                               type=bool)
        self.isLimitPreviewEnabled = settings.value(
            constants.PREVIEW_LIMIT_ENABLED, type=bool)
        self.maxLimitPreview = settings.value(constants.PREVIEW_LIMIT_MAX,
                                              type=int)

        self.isEmittingPoint = False

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

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

    def next(self):
        if self.step == 0:
            self.messageBarUtils.showMessage(
                self.MESSAGE_HEADER,
                "Select base point of displacement",
                duration=0)
        elif self.step == 1:
            self.messageBarUtils.removeAllMessages()
            self.messageBarUtils.showButton(
                self.MESSAGE_HEADER,
                "Select second point of displacement",
                "Done",
                buttonCallback=self.done)
        elif self.step == 2:
            totalLayers = 0
            layersWithNoSelection = 0

            layers = self.iface.legendInterface().selectedLayers()
            for layer in layers:
                if layer.type() == QgsMapLayer.VectorLayer:
                    totalLayers += 1
                    if layer.selectedFeatureCount() == 0:
                        layersWithNoSelection += 1

            if totalLayers == layersWithNoSelection:
                self.messageBarUtils.showMessage(
                    self.MESSAGE_HEADER,
                    "None of the layers selected in the TOC has a selection. Nothing done",
                    QgsMessageBar.WARNING)
                self.iface.mapCanvas().refresh()
                self.iface.mapCanvas().unsetMapTool(self)
                return
            elif layersWithNoSelection != 0:
                self.messageBarUtils.showYesCancel(
                    self.MESSAGE_HEADER,
                    "%d out of %d vector layers have no selection. Proceed on the other layers?"
                    % (layersWithNoSelection, totalLayers),
                    QgsMessageBar.WARNING, self.doMove, self.deactivate)
                return

            self.doCopy()

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

        self.rubberBandGeometriesPreview.reset(QGis.Line)
        self.rubberBandPointsPreview.reset(QGis.Point)

        currentLayer = None
        isEditable = []

        try:
            ok = self.prepareDisplacement()
            if not ok:
                self.messageBarUtils.showMessage(self.MESSAGE_HEADER,
                                                 "Invalid displacement.",
                                                 QgsMessageBar.WARNING,
                                                 duration=5)
                return

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

            inGeom = QgsGeometry()
            outFeat = QgsFeature()

            current = 0

            layers = self.iface.legendInterface().selectedLayers()
            for layer in layers:
                # only deal with selected features of the selected layers
                if layer.type(
                ) == QgsMapLayer.VectorLayer and layer.selectedFeatureCount(
                ) > 0:
                    currentLayer = layer
                    if layer.isEditable():
                        isEditable.append(True)
                        layer.beginEditCommand("Copy from multiple layers")
                    else:
                        isEditable.append(False)
                        layer.startEditing()

                    crsDest = layer.crs()
                    canvas = self.iface.mapCanvas()
                    mapRenderer = canvas.mapSettings()
                    crsSrc = mapRenderer.destinationCrs()
                    crsTransform = QgsCoordinateTransform(crsSrc, crsDest)
                    # cannot indicate direction in QgsGeometry.transform
                    crsTransformReverse = QgsCoordinateTransform(
                        crsDest, crsSrc)

                    features = vectorlayerutils.features(layer)
                    total = 100.0 / float(len(features))
                    pgidIndex = layer.fieldNameIndex("gid")
                    progress.setValue(0)
                    for f in features:
                        inGeom = f.geometry()
                        attrs = f.attributes()

                        # perform displacement in map coordinates instead of layer coordinates
                        inGeom.transform(crsTransformReverse)
                        outGeom = self.moveGeometry(inGeom)
                        outGeom.transform(crsTransform)

                        outFeat.initAttributes(len(attrs))
                        for index in range(len(attrs)):
                            if index <> pgidIndex:
                                outFeat.setAttribute(index, attrs[index])
                        outFeat.setGeometry(outGeom)
                        layer.addFeature(outFeat)

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

            currentLayer = None
            self.messageBarUtils.showMessage(self.MESSAGE_HEADER, "Success",
                                             QgsMessageBar.INFO, 5)
            # commit modifications in all layers at the end
            count = 0
            layers = self.iface.legendInterface().selectedLayers()
            for layer in layers:
                # only deal with selected features of the selected layers
                if layer.type(
                ) == QgsMapLayer.VectorLayer and layer.selectedFeatureCount(
                ) > 0:
                    if isEditable[count]:
                        layer.endEditCommand()
                    else:
                        layer.commitChanges()

                    count += 1

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

            # rollback modifications in all layers
            count = 0
            layers = self.iface.legendInterface().selectedLayers()
            for layer in layers:
                # only deal with selected features of the selected layers
                if layer.type(
                ) == QgsMapLayer.VectorLayer and layer.selectedFeatureCount(
                ) > 0:
                    if isEditable[count]:
                        layer.destroyEditCommand()
                    else:
                        layer.rollBack()
                    # subsequent layers have not been modified
                    if layer == currentLayer:
                        break

                    count += 1

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

        self.resetCopyMultipleLayers()
        self.next()

    def resetCopyMultipleLayers(self):
        self.step = 1

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

    def prepareDisplacement(self):
        # rubber bands are in crs of map canvas
        self.dx = self.endPoint.x() - self.startPoint.x()
        self.dy = self.endPoint.y() - self.startPoint.y()
        return True

    def moveGeometry(self, geom):
        geom.translate(self.dx, self.dy)
        return geom

    def moveVertex(self, vertex):
        vertex.setX(vertex.x() + self.dx)
        vertex.setY(vertex.y() + self.dy)
        return vertex

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        pass

    def canvasReleaseEvent(self, e):
        if e.button() == Qt.RightButton:
            if self.step == 1:
                self.done()

        if self.step == 0:

            # we snap to the current layer (we don't have exclude points and use the tolerances from the qgis properties)
            pos = QPoint(e.pos().x(), e.pos().y())
            result = self.snap(pos)
            if result != []:
                self.startPoint = QgsPoint(result[0].snappedVertex)
            else:
                self.startPoint = self.toMapCoordinates(e.pos())

            self.endPoint = self.startPoint
            self.isEmittingPoint = True

            self.rubberBandMoveAxis.reset(QGis.Line)
            self.rubberBandMoveAxis.addPoint(self.startPoint)
            self.rubberBandMoveAxis.addPoint(self.endPoint)
            self.rubberBandMoveAxis.show()

            self.step = 1
            self.next()
        elif self.step == 1:
            pos = QPoint(e.pos().x(), e.pos().y())
            result = self.snap(pos)
            if result != []:
                self.endPoint = QgsPoint(result[0].snappedVertex)
            else:
                self.endPoint = self.toMapCoordinates(e.pos())

            self.step = 2
            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)

        if not self.isEmittingPoint:
            return

        if self.step == 1:
            if result != []:
                self.endPoint = QgsPoint(result[0].snappedVertex)
            else:
                self.endPoint = self.toMapCoordinates(e.pos())
            self.rubberBandMoveAxis.reset(QGis.Line)
            self.rubberBandMoveAxis.addPoint(self.startPoint)
            self.rubberBandMoveAxis.addPoint(self.endPoint)
            self.rubberBandMoveAxis.show()

            self.updateGeometriesPreview()

    def updateGeometriesPreview(self):
        ok = self.prepareDisplacement()
        self.rubberBandGeometriesPreview.reset(QGis.Line)
        self.rubberBandPointsPreview.reset(QGis.Point)

        if not ok:
            return

        if not self.isPreviewEnabled:
            return

        layers = self.iface.legendInterface().selectedLayers()
        for layer in layers:
            if layer.type(
            ) == QgsMapLayer.VectorLayer and layer.selectedFeatureCount() > 0:
                crsDest = layer.crs()
                canvas = self.iface.mapCanvas()
                mapRenderer = canvas.mapSettings()
                crsSrc = mapRenderer.destinationCrs()
                crsTransformReverse = QgsCoordinateTransform(crsDest, crsSrc)

                if self.isLimitPreviewEnabled:
                    maxCount = self.maxLimitPreview
                else:
                    maxCount = layer.featureCount()

                currentCount = 0
                features = vectorlayerutils.features(layer)
                for f in features:
                    inGeom = f.geometry()
                    inGeom.transform(crsTransformReverse)
                    inGeom = self.moveGeometry(inGeom)
                    # in map coordinates
                    if inGeom.type() == QGis.Point:
                        self.rubberBandPointsPreview.addGeometry(inGeom, None)
                    else:
                        self.rubberBandGeometriesPreview.addGeometry(
                            inGeom, None)

                    currentCount += 1
                    if currentCount >= maxCount:
                        break

        self.rubberBandPointsPreview.show()
        self.rubberBandGeometriesPreview.show()

    def snap(self, pos):
        if self.digitizingTools.isBackgroundSnapping:
            (_, result) = self.snapper.snapToBackgroundLayers(pos)
        else:
            (_,
             result) = self.snapper.snapToCurrentLayer(pos,
                                                       QgsSnapper.SnapToVertex)
        return result
Esempio n. 5
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)
Esempio n. 6
0
class ExtendDigitizingMode(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        QgsMapToolEmitPoint.__init__(self, self.iface.mapCanvas())

        self.messageBarUtils = MessageBarUtils(iface)

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

        self.layer = None
        self.reset()

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

    def reset(self, clearMessages=True):
        self.step = 0
        self.snappingResultBoundaryEdge = None
        self.snappingResultExtendEdge = None
        self.cachedSpatialIndex = None

        self.rubberBandBoundary.reset(QGis.Line)

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

        if clearMessages:
            self.messageBarUtils.removeAllMessages()

    def resetExtend(self, full=False):
        if full:
            self.reset(False)
        else:
            # jsut the last step
            self.step = 1
            self.snappingResultExtendEdge = None

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

    def next(self):
        if self.step == 0:
            # always starts at this step: test if there is a selection
            if self.layer.selectedFeatureCount() > 0:
                # next
                self.step = 1
                self.next()
                return
            self.messageBarUtils.showButton("Extend",
                                            "Select boundary edge",
                                            "Use full layer",
                                            buttonCallback=self.useFullLayer)
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.boundaryEdgeFound)
        elif self.step == 1:
            self.messageBarUtils.showButton("Extend",
                                            "Select edge to extend",
                                            "Done",
                                            buttonCallback=self.done)
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.extendEdgeFound)
        elif self.step == 2:
            self.doExtend()

    def boundaryEdgeFound(self, result, pointClicked):
        self.snappingResultBoundaryEdge = result
        self.segmentFinderTool.segmentFound.disconnect(self.boundaryEdgeFound)
        self.segmentFinderTool.deactivate()

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

        self.step = 1
        self.next()

    def extendEdgeFound(self, result, pointClicked):
        self.snappingResultExtendEdge = result
        self.segmentFinderTool.segmentFound.disconnect(self.extendEdgeFound)
        self.segmentFinderTool.deactivate()

        transform = QgsCoordinateTransform(
            self.iface.mapCanvas().mapSettings().destinationCrs(),
            self.layer.crs())
        g = QgsGeometry.fromPoint(pointClicked)
        g.transform(transform)
        self.pointExtend = g.asPoint()

        self.step = 2
        self.next()

    def doExtend(self):
        self.messageBarUtils.showMessage("Extend", "Running...", duration=0)
        extendUtils = ExtendUtils(self.iface)
        try:
            featureExtend = self._getFeature(self.snappingResultExtendEdge)
            geometryExtend = featureExtend.geometry()
            bvExtend = geometryExtend.vertexAt(
                self.snappingResultExtendEdge.beforeVertexNr)
            avExtend = geometryExtend.vertexAt(
                self.snappingResultExtendEdge.afterVertexNr)

            vertexNrToMove = extendUtils.vertexIndexToMove(
                bvExtend,
                avExtend,
                self.snappingResultExtendEdge,
                self.pointExtend,
                mustExtend=False)
            vertexToMove = geometryExtend.vertexAt(vertexNrToMove)

            testRay = self.getTestRayForExtend(extendUtils, geometryExtend,
                                               bvExtend, avExtend,
                                               vertexNrToMove)
            # 3 cases: selection on layer, whole layer, cutting edge
            if self.layer.selectedFeatureCount(
            ) > 0 or not self.snappingResultBoundaryEdge:
                # use same strategy for both
                pointIntersection, alreadyNearEdge = self.getExtendInformationFromLayer(
                    extendUtils, testRay, vertexToMove)
            else:
                # no ambiguity here
                pointIntersection, alreadyNearEdge = self.getExtendInformationFromSnappedBoundaryEdge(
                    extendUtils, testRay, vertexToMove)

            if alreadyNearEdge:
                self.messageBarUtils.showMessage(
                    "Extend",
                    "Nothing done: Already at boundary edge",
                    QgsMessageBar.WARNING,
                    duration=2)
            elif pointIntersection:
                if self.cachedSpatialIndex:
                    self.cachedSpatialIndex.deleteFeature(featureExtend)

                fid = featureExtend.id()
                geometryExtend.moveVertex(pointIntersection.x(),
                                          pointIntersection.y(),
                                          vertexNrToMove)
                self.layer.beginEditCommand("Extend")
                self.layer.changeGeometry(fid, geometryExtend)
                if self.cachedSpatialIndex:
                    self.cachedSpatialIndex.insertFeature(featureExtend)
                self.layer.endEditCommand()

                self.iface.mapCanvas().refresh()

                self.messageBarUtils.showMessage(
                    "Extend",
                    "The segment was extended successfully",
                    duration=2)
            else:
                self.messageBarUtils.showMessage(
                    "Extend",
                    "Nothing done: No suitable boundary edge",
                    QgsMessageBar.WARNING,
                    duration=2)
        except Exception as e:
            QgsMessageLog.logMessage(repr(e))
            self.messageBarUtils.showMessage(
                "Trim",
                "There was an error performing this command. See QGIS Message log for details",
                QgsMessageBar.CRITICAL,
                duration=5)

        # select another edge to extend
        self.resetExtend()
        self.next()

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

    def buildSpatialIndex(self):
        if not self.cachedSpatialIndex:
            # Build the spatial index for faster lookup.
            index = QgsSpatialIndex()
            for f in vectorlayerutils.features(self.layer):
                index.insertFeature(f)
            self.cachedSpatialIndex = index

    def isNearEdgeInLayer(self, point, distance):
        bufferGeometry = QgsGeometry.fromPoint(point).buffer(distance, 2)
        fids = self.cachedSpatialIndex.intersects(bufferGeometry.boundingBox())
        for fid in fids:
            if fid == self.snappingResultExtendEdge.snappedAtGeometry:
                continue
            feature = self.layer.getFeatures(QgsFeatureRequest(fid)).next()
            geometryTest = feature.geometry()
            if bufferGeometry.intersects(geometryTest):
                return True
        return False

    def getExtendInformationFromLayer(self, extendUtils, testRay,
                                      vertexToMove):
        self.buildSpatialIndex()

        if self.isNearEdgeInLayer(vertexToMove, constants.TOLERANCE_DEGREE):
            return (None, True)

        # get segment
        fids = self.cachedSpatialIndex.intersects(testRay.boundingBox())
        candidates = []
        for fid in fids:
            if fid == self.snappingResultExtendEdge.snappedAtGeometry:
                continue
            feature = self.layer.getFeatures(QgsFeatureRequest(fid)).next()
            geometryTest = feature.geometry()
            if testRay.intersects(geometryTest):
                candidates.extend(
                    vectorlayerutils.segmentIntersectionPoint(
                        geometryTest, testRay))

        # extend as short as possible among the candidate intersection points
        if len(candidates) > 0:
            distancesToVertexToMove = map(
                lambda x: self.sqDistance(vertexToMove, x), candidates)
            closestIndices = sorted(range(len(distancesToVertexToMove)),
                                    key=lambda i: distancesToVertexToMove[i])
            return (candidates[closestIndices[0]], False)

        return (None, False)

    def isNearSingleEdge(self, point, edge, distance):
        bufferGeometry = QgsGeometry.fromPoint(point).buffer(distance, 2)
        return edge.intersects(bufferGeometry)

    def getExtendInformationFromSnappedBoundaryEdge(self, extendUtils, testRay,
                                                    vertexToMove):
        featureBoundary = self._getFeature(self.snappingResultBoundaryEdge)
        geometryBoundary = featureBoundary.geometry()
        bvBoundary = geometryBoundary.vertexAt(
            self.snappingResultBoundaryEdge.beforeVertexNr)
        avBoundary = geometryBoundary.vertexAt(
            self.snappingResultBoundaryEdge.afterVertexNr)
        edgeBoundary = QgsGeometry.fromPolyline([bvBoundary, avBoundary])

        if self.isNearSingleEdge(vertexToMove, edgeBoundary,
                                 constants.TOLERANCE_DEGREE):
            return (None, True)

        if testRay.intersects(edgeBoundary):
            # only one possible intersection point (2 edges)
            pointIntersection = vectorlayerutils.segmentIntersectionPoint(
                testRay, edgeBoundary)
            if pointIntersection:
                pointIntersection = pointIntersection[0]
            return (pointIntersection, False)

        return (None, False)

    def getTestRayForExtend(self, extendUtils, geometryExtend, bvExtend,
                            avExtend, vertexNrToMove):
        # Create a segment that goes away from the edgeExtend, starting at vertexNrToMove
        # and ending at the bounding box of the layer
        if vertexNrToMove == self.snappingResultExtendEdge.afterVertexNr:
            baseVertex = avExtend
            # direction towards which the extension will be performed
            vectorDirection = [bvExtend, avExtend]
        else:
            baseVertex = bvExtend
            vectorDirection = [avExtend, bvExtend]

        boundingBox = self.layer.extent()
        topLeft = QgsPoint(boundingBox.xMinimum(), boundingBox.yMaximum())
        topRight = QgsPoint(boundingBox.xMaximum(), boundingBox.yMaximum())
        bottomRight = QgsPoint(boundingBox.xMaximum(), boundingBox.yMinimum())
        bottomLeft = QgsPoint(boundingBox.xMinimum(), boundingBox.yMinimum())

        topIntersection = extendUtils.intersectionPoint(
            bvExtend, avExtend, topLeft, topRight)
        # check that the vector from baseVertex to that point is in the same direction as
        # vector direction
        if topIntersection and self.dp(vectorDirection[0], vectorDirection[1],
                                       baseVertex, topIntersection) > 0:
            return QgsGeometry.fromPolyline([baseVertex, topIntersection])

        rightIntersection = extendUtils.intersectionPoint(
            bvExtend, avExtend, topRight, bottomRight)
        if rightIntersection and self.dp(vectorDirection[0],
                                         vectorDirection[1], baseVertex,
                                         rightIntersection) > 0:
            return QgsGeometry.fromPolyline([baseVertex, rightIntersection])

        bottomIntersection = extendUtils.intersectionPoint(
            bvExtend, avExtend, bottomRight, bottomLeft)
        if bottomIntersection and self.dp(vectorDirection[0],
                                          vectorDirection[1], baseVertex,
                                          bottomIntersection) > 0:
            return QgsGeometry.fromPolyline([baseVertex, bottomIntersection])

        leftIntersection = extendUtils.intersectionPoint(
            bvExtend, avExtend, bottomLeft, topLeft)
        if leftIntersection and self.dp(vectorDirection[0], vectorDirection[1],
                                        baseVertex, leftIntersection) > 0:
            return QgsGeometry.fromPolyline([baseVertex, leftIntersection])

    def dp(self, pt11, pt12, pt21, pt22):
        return (pt12.x() - pt11.x()) * (pt22.x() - pt21.x()) + (
            pt12.y() - pt11.y()) * (pt22.y() - pt21.y())

    def sqDistance(self, pt1, pt2):
        return (pt1.x() - pt2.x())**2 + (pt1.y() - pt2.y())**2

    def useFullLayer(self):
        self.snappingResultBoundaryEdge = None
        self.segmentFinderTool.segmentFound.disconnect(self.boundaryEdgeFound)
        self.segmentFinderTool.deactivate()

        self.step = 1
        self.next()

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

    def enter(self):
        #ignore
        pass

    def canvasPressEvent(self, e):
        # use right button instead of clicking on button in message bar
        if e.button() == Qt.RightButton:
            if self.step == 0:
                self.useFullLayer()
                return
            elif self.step == 1:
                self.resetExtend(True)
                self.next()
                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)
Esempio n. 7
0
class TrimDigitizingMode(QgsMapToolEmitPoint):
    def __init__(self, iface):
        self.iface = iface
        QgsMapToolEmitPoint.__init__(self, self.iface.mapCanvas())
        
        self.messageBarUtils = MessageBarUtils(iface)
        
        self.segmentFinderTool = SegmentFinderTool(self.iface.mapCanvas())
        self.rubberBandCutting = QgsRubberBand(self.iface.mapCanvas())
        self.rubberBandCutting.setColor(QColor(255, 0, 0))
        self.rubberBandCutting.setWidth(2)
        
        self.layer = None
        self.reset()
    
    def setLayer(self, layer):
        self.layer = layer
        
    def reset(self, clearMessages = True):
        self.step = 0
        self.snappingResultCuttingEdge = None
        self.snappingResultTrim = None
        self.cachedSpatialIndex = None
        
        self.rubberBandCutting.reset(QGis.Line)
        
        try:self.segmentFinderTool.segmentFound.disconnect()
        except Exception: pass
        
        if clearMessages:
            self.messageBarUtils.removeAllMessages()
            
    def resetTrim(self):
        self.step = 1
        self.snappingResultTrim = None
        
    def deactivate(self):
        self.reset()
        QgsMapToolEmitPoint.deactivate(self)
        
    def next(self):
        if self.step == 0:
                        
            # alwways starts at this step: test if there is a selection
            if self.layer.selectedFeatureCount() > 0:
                # next
                self.step = 1
                self.next()
                return
            self.messageBarUtils.showButton("Trim", "Select cutting edge", "Use full layer", buttonCallback=self.useFullLayer)
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.cuttingEdgeFound)
        elif self.step == 1:
            self.messageBarUtils.showButton("Trim", "Select segment to trim","Done",buttonCallback=self.done)
            self.currentMapTool = self.segmentFinderTool
            self.segmentFinderTool.segmentFound.connect(self.trimEdgeFound)
        elif self.step == 2:
            self.doTrim()
         
    def cuttingEdgeFound(self, result, pointClicked):
        self.snappingResultCuttingEdge = result
        self.segmentFinderTool.segmentFound.disconnect(self.cuttingEdgeFound)
        self.segmentFinderTool.deactivate()
        
        self.rubberBandCutting.reset(QGis.Line)
        self.rubberBandCutting.addPoint(result.beforeVertex)
        self.rubberBandCutting.addPoint(result.afterVertex, True)
        self.rubberBandCutting.show()
        
        self.step = 1
        self.next()
        
    def trimEdgeFound(self, result, pointClicked):
        self.snappingResultTrim = result
        self.segmentFinderTool.segmentFound.disconnect(self.trimEdgeFound)
        self.segmentFinderTool.deactivate()
        
        transform = QgsCoordinateTransform(self.iface.mapCanvas().mapSettings().destinationCrs(), self.layer.crs())
        g = QgsGeometry.fromPoint(pointClicked)
        g.transform(transform)
        self.pointTrim = g.asPoint()
        
        self.step = 2
        self.next()
   
    def doTrim(self):
        self.messageBarUtils.showMessage("Trim", "Running...", duration=0)
        trimUtils = TrimUtils(self.iface)
        try:
            qDebug("New Trim")
            # 3 cases: selection on layer, whole layer, cutting edge
            if self.layer.selectedFeatureCount() > 0 or not self.snappingResultCuttingEdge:
                # use same strategy for both
                indexReferenceIntersection, pointIntersections  = self.getTrimmingInformationFromLayer(trimUtils)
            else:
                # no ambiguity here
                indexReferenceIntersection, pointIntersections = self.getTrimmingInformationFromSnappedGeometry(trimUtils)
                
            if indexReferenceIntersection != None:
                featureTrim = self._getFeature(self.snappingResultTrim)
                geometryTrim = featureTrim.geometry()
            
                parts, unconcernedParts = trimUtils.removeVertices(geometryTrim, indexReferenceIntersection, pointIntersections, 
                                                  self.snappingResultTrim.afterVertexNr, self.pointTrim)
                
                parts = self.cleanupTrimmedParts(parts)
                if len(parts) > 0:
                    if unconcernedParts:
                        parts.extend(unconcernedParts)
                    
                    # replace geometry
                    self.layer.beginEditCommand("Trim")
                    
                    # geometry updated => remove from index (readded very soon)
                    if self.cachedSpatialIndex:
                        self.cachedSpatialIndex.deleteFeature(featureTrim)
                    
                    # by default create a multipart geometry (unless not supported by data provider)
                    # for ex PostGIS layer can be constrained to be multipart
                    # some data types can have mixed multi/single (ex shp)
                    if (geometryTrim.isMultipart() or len(parts) > 1) and QGis.isMultiType(self.layer.dataProvider().geometryType()) :
                        qDebug("Multipart")
                        geometryTrim = QgsGeometry.fromMultiPolyline(map(lambda x:x.asPolyline(),parts))
                        self.layer.changeGeometry(featureTrim.id(), geometryTrim)
                        # for some reason, the line above makes it so the feature cannot be deleted from the index the next time
                        featureTrim.setGeometry(geometryTrim)
                        if self.cachedSpatialIndex:
                            self.cachedSpatialIndex.insertFeature(featureTrim)
                    else:
                        qDebug("Singlepart")
                        self.layer.deleteFeature(featureTrim.id())
                        # create a new feature for each part (so still single part geometries)
                        for part in parts:
                            featureTrim.setGeometry(part)
                            self.layer.addFeature(featureTrim)
                            if self.cachedSpatialIndex:
                                self.cachedSpatialIndex.insertFeature(featureTrim)
                        
                    self.layer.endEditCommand()
                    
                    self.iface.mapCanvas().refresh()
            
                    self.messageBarUtils.showMessage("Trim", "The segment was trimmed successfully", duration=2)
                else:
                    self.messageBarUtils.showMessage("Trim", "Nothing done: Trimming would create an invalid geometry", QgsMessageBar.WARNING, duration=2)
            else:
                self.messageBarUtils.showMessage("Trim", "Nothing done: The geometries are not intersecting", QgsMessageBar.WARNING, duration=2)
        except Exception as e:
            QgsMessageLog.logMessage(repr(e))
            self.messageBarUtils.showMessage("Trim", "There was an error performing this command. See QGIS Message log for details", QgsMessageBar.CRITICAL, duration=5)   
            self.layer.destroyEditCommand()
            self.done()
            
        self.resetTrim()
        self.next()
            
    def cleanupTrimmedParts(self, parts):
        return filter(lambda x: x and x.length() > constants.TOLERANCE_DEGREE*2, parts)
    
    def _getFeature(self, snappingResult):
        fid = snappingResult.snappedAtGeometry
        feature = QgsFeature()
        fiter = self.layer.getFeatures(QgsFeatureRequest(fid))
        if fiter.nextFeature(feature):
            return feature
        return None 
    
    def _getSegment(self, snappingResult):
        feature = self._getFeature(snappingResult)
        geometry = feature.geometry()
        bv = geometry.vertexAt(snappingResult.beforeVertexNr)
        av = geometry.vertexAt(snappingResult.afterVertexNr)
        return QgsGeometry.fromPolyline([bv,av])
    
    def getTrimmingInformationFromSnappedGeometry(self, trimUtils):   
        edgeCuttingEdge = self._getSegment(self.snappingResultCuttingEdge)
        featureTrim = self._getFeature(self.snappingResultTrim)
        geometryTrim = featureTrim.geometry()
        
        bufferGeometryTrim = geometryTrim.buffer(constants.TOLERANCE_DEGREE, 2)
        
        if bufferGeometryTrim.intersects(edgeCuttingEdge):
            candidates = trimUtils.intersectionPointWithTolerance(edgeCuttingEdge, bufferGeometryTrim, geometryTrim)
            
        if len(candidates) > 0:
            index = self.indexOfClosestIntersection(candidates)
            return index, candidates
        return (None, None)
    
    def buildSpatialIndex(self):
        if not self.cachedSpatialIndex:
            # Build the spatial index for faster lookup.
            index = QgsSpatialIndex()
            for f in vectorlayerutils.features(self.layer):
                index.insertFeature(f)
            self.cachedSpatialIndex = index 
    
    def getTrimmingInformationFromLayer(self, trimUtils):
        try:
            self.buildSpatialIndex()
                
            # get geometry
            featureTrim = self._getFeature(self.snappingResultTrim)
            geometryTrim = featureTrim.geometry()
            
            # use buffered geometry to solve unexact geoemtries because of limited floating 
            # precision (point on a line never exactly on the line) 
            # (sometimes no intersection is present when there should be)
            bufferGeometryTrim = geometryTrim.buffer(constants.TOLERANCE_DEGREE, 2)
            
            fids = self.cachedSpatialIndex.intersects(bufferGeometryTrim.boundingBox())
            if  len(fids) > 0:
                candidates = []
                for fid in fids:
                    if fid == self.snappingResultTrim.snappedAtGeometry:
                        continue
                    features = self.layer.getFeatures(QgsFeatureRequest(fid))
                    feature = features.next()
                    geometryTest = feature.geometry()
                    if bufferGeometryTrim.intersects(geometryTest):
                        pointIntersection = trimUtils.intersectionPointWithTolerance(geometryTest, bufferGeometryTrim, geometryTrim)
                        candidates.extend(pointIntersection)
                        
                if len(candidates) > 0:
                    index = self.indexOfClosestIntersection(candidates)
                    return index, candidates
                
            return (None, None)
        except StopIteration:
            # index is invalid maybe because undo was performed: force rebuild
            # and relaunch
            self.cachedSpatialIndex = None
            return self.getTrimmingInformationFromLayer(trimUtils)
    
    def indexOfClosestIntersection(self, candidates):
        distancesToSnappedTrimPoint = map(lambda x: sqDistance(self.pointTrim, x), candidates)
        closestIndices = sorted(range(len(distancesToSnappedTrimPoint)),key=lambda i:distancesToSnappedTrimPoint[i])
        return closestIndices[0]
    
    def useFullLayer(self):
        self.snappingResultCuttingEdge = None
        # cleanup
        self.segmentFinderTool.segmentFound.disconnect(self.cuttingEdgeFound)
        self.segmentFinderTool.deactivate()
        
        self.step = 1
        self.next()
        
    def done(self):
        self.reset()
        self.iface.mapCanvas().unsetMapTool(self)
        
    def enter(self):
        #ignore
        pass
    
    def canvasPressEvent(self, e):
        # use right button instead of clicking on button in message bar
        if e.button() == Qt.RightButton:
            if self.step == 0:
                self.useFullLayer()
                return
            elif self.step == 1:
                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)