Ejemplo n.º 1
0
class MoveFeature():
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n',
                                   'MoveFeature_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Move Features')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('MoveFeature', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToVectorMenu(self.menu, action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/moving_feature/icon.png'
        self.add_action(icon_path,
                        text=self.tr(u'Move Features'),
                        callback=self.run,
                        parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginVectorMenu(self.tr(u'&Move Features'),
                                              action)
            self.iface.removeToolBarIcon(action)

    def getLayers(self):
        self.dlg.cb_layers.clear()
        self.dlg.cb_source.clear()
        self.dlg.cb_target.clear()

        self.dlg.cb_layers.addItems(self.layers.keys())
        self.dlg.cb_source.addItems(
            [k for k, v in self.layers.items() if v.wkbType() == 1])
        self.dlg.cb_target.addItems(
            [k for k, v in self.layers.items() if v.wkbType() == 1])

        self.startName = self.dlg.cb_source.currentText()
        self.endName = self.dlg.cb_target.currentText()
        try:
            self.startLayer = self.layers[self.startName]
            self.geomStart = next(self.startLayer.getFeatures()).geometry()
            self.sx, self.sy = self.geomStart.asPoint()
            self.dlg.sb_source_x.setValue(self.sx)
            self.dlg.sb_source_y.setValue(self.sy)

            self.endLayer = self.layers[self.endName]
            self.geomEnd = next(self.endLayer.getFeatures()).geometry()
            self.ex, self.ey = self.geomEnd.asPoint()
            self.dlg.sb_target_x.setValue(self.ex)
            self.dlg.sb_target_y.setValue(self.ey)
        except:
            pass

    def select_output(self):
        self.dlg.lineEdit.setText("")
        self.shpPath, self._filter = QFileDialog.getSaveFileName(
            self.dlg, "Select input shp file", "", '*.shp')
        self.dlg.lineEdit.setText(self.shpPath)

    def convertFeatToJson(self, feat):
        self.fjson = {'type': 'Feature'}
        self.fjson["id"] = feat.id()
        self.fjson["geometry"] = feat.geometry()
        self.fjson["properties"] = [
            (field.type(), field.name(), attr)
            for field, attr in zip(feat.fields(), feat.attributes())
        ]
        return self.fjson

    def display_point(self, pointTool, pnt):
        try:
            self.coorx = round(pointTool.x(), 6)
            self.coory = round(pointTool.y(), 6)
            if pnt == "start":
                self.dlg.sb_source_x.setValue(self.coorx)
                self.dlg.sb_source_y.setValue(self.coory)
            elif pnt == "end":
                self.dlg.sb_target_x.setValue(self.coorx)
                self.dlg.sb_target_y.setValue(self.coory)
        except AttributeError:
            pass
        self.unset()
        self.dlg.showNormal()

    def getClickedCoor(self, pnt):
        self.dlg.showMinimized()
        global pointTool
        pointTool = QgsMapToolEmitPoint(self.canvas)
        pointTool.canvasClicked.connect(lambda x: self.display_point(x, pnt))
        self.canvas.setMapTool(pointTool)

    def unset(self):
        self.canvas.unsetMapTool(pointTool)

    def getCoors(self):
        self.sender = self.dlg.sender()
        self.oname = self.sender.objectName()

        if self.oname == "cb_source":
            self.startName = self.dlg.cb_source.currentText()
            self.startLayer = self.layers[self.startName]
            self.geomStart = next(self.startLayer.getFeatures()).geometry()
            self.sx, self.sy = self.geomStart.asPoint()
            self.dlg.sb_source_x.setValue(self.sx)
            self.dlg.sb_source_y.setValue(self.sy)

        elif self.oname == "cb_target":
            self.endName = self.dlg.cb_target.currentText()
            self.endLayer = self.layers[self.endName]
            self.geomEnd = next(self.endLayer.getFeatures()).geometry()
            self.ex, self.ey = self.geomEnd.asPoint()
            self.dlg.sb_target_x.setValue(self.ex)
            self.dlg.sb_target_y.setValue(self.ey)

    def getDeltas(self):
        self.dlg.sb_dx.setValue(self.dlg.sb_target_x.value() -
                                self.dlg.sb_source_x.value())
        self.dlg.sb_dy.setValue(self.dlg.sb_target_y.value() -
                                self.dlg.sb_source_y.value())

    def move_feature(self, feat, dx, dy):
        self.new_feat = feat.copy()
        self.geometry = feat["geometry"]
        if self.geometry.wkbType() == 1:  # Point
            self.geom = self.geometry.asPoint()
            self.newGeom = QgsPointXY(self.geom[0] + dx, self.geom[1] + dy)
            self.newGeometry = QgsGeometry.fromPointXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 2:  # LineString
            self.geom = self.geometry.asPolyline()
            self.newGeom = [
                QgsPointXY(i[0] + dx, i[1] + dy) for i in self.geom
            ]
            self.newGeometry = QgsGeometry.fromPolylineXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 3:  # Polygon
            self.geom = self.geometry.buffer(0, 5).asPolygon()
            self.newGeom = []
            for g in self.geom:
                self.g1 = []
                for gg in g:
                    self.new_gg = QgsPointXY(gg[0] + dx, gg[1] + dy)
                    self.g1.append(self.new_gg)
                self.newGeom.append(self.g1)

            self.newGeometry = QgsGeometry.fromPolygonXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 4:  # MultiPoint
            self.geom = self.geometry.asMultiPoint()
            self.newGeom = [
                QgsPointXY(i[0] + dx, i[1] + dy) for i in self.geom
            ]
            self.newGeometry = QgsGeometry.fromMultiPointXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 5:  # MultiLineString
            self.geom = self.geometry.asMultiPolyline()
            self.newGeom = []
            for g in self.geom:
                self.g1 = []
                for gg in g:
                    self.new_gg = QgsPointXY(gg[0] + dx, gg[1] + dy)
                    self.g1.append(self.new_gg)
                self.newGeom.append(self.g1)

            self.newGeometry = QgsGeometry.fromMultiPolylineXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 6:  # MultiPolygon
            self.geom = self.geometry.asMultiPolygon()
            self.newGeom = []
            for g in self.geom:
                self.g1 = []
                for gg in g:
                    self.g2 = []
                    for ggg in gg:
                        self.new_ggg = QgsPointXY(ggg[0] + dx, ggg[1] + dy)
                        self.g2.append(self.new_ggg)
                    self.g1.append(self.g2)
                self.newGeom.append(self.g1)

            self.newGeometry = QgsGeometry.fromMultiPolygonXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        return self.new_feat

    def createShp(self, out_shp, features, inLayer, sr, out_type=None):
        self.new_name = inLayer.name() + "_new"
        self.geomType = QgsWkbTypes.displayString(inLayer.wkbType())
        if out_type == "m":
            self.new_shp = QgsVectorLayer(self.geomType, self.new_name,
                                          "memory")
            self.pr = self.new_shp.dataProvider()

            self.fields = [
                QgsField(n, t) for t, n, _ in features[0]["properties"]
            ]
            self.pr.addAttributes(self.fields)
            self.new_shp.updateFields()

            for f in features:
                self.feat = QgsFeature()
                self.feat.setGeometry(f["geometry"])
                self.attr = [k for i, j, k in f["properties"]]
                self.feat.setAttributes(self.attr)
                self.pr.addFeature(self.feat)
                self.new_shp.updateExtents()
            QgsProject.instance().addMapLayer(self.new_shp)
            self.new_shp.setCrs(QgsCoordinateReferenceSystem.fromWkt(sr))

        else:
            self.fields = QgsFields()
            for t, n, _ in features[0]["properties"]:
                self.fields.append(QgsField(n, t))

            self.writer = QgsVectorFileWriter(
                out_shp, 'UTF-8', self.fields, inLayer.wkbType(),
                QgsCoordinateReferenceSystem.fromWkt(sr), 'ESRI Shapefile')
            for f in features:
                self.feat = QgsFeature()
                self.feat.setGeometry(f["geometry"])
                self.attr = [k for i, j, k in f["properties"]]
                self.feat.setAttributes(self.attr)
                self.writer.addFeature(self.feat)

            self.layer = iface.addVectorLayer(out_shp, '', 'ogr')
            self.layer.setExtent(inLayer.extent())
            del (self.writer)

    def run(self):
        """Run method that performs all the real work"""

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.dlg = MoveFeatureDialog()
            self.layers = {
                layer.name(): layer
                for layer in QgsProject.instance().mapLayers().values()
                if layer.type() == 0
            }
            self.getLayers()

            self.dlg.btn_source.clicked.connect(
                lambda x: self.getClickedCoor("start"))
            self.dlg.btn_target.clicked.connect(
                lambda x: self.getClickedCoor("end"))

            self.dlg.cb_source.currentTextChanged.connect(self.getCoors)
            self.dlg.cb_target.currentTextChanged.connect(self.getCoors)
            self.dlg.sb_source_x.valueChanged.connect(self.getDeltas)
            self.dlg.sb_source_y.valueChanged.connect(self.getDeltas)
            self.dlg.sb_target_x.valueChanged.connect(self.getDeltas)
            self.dlg.sb_target_y.valueChanged.connect(self.getDeltas)

            self.dlg.toolButton.clicked.connect(self.select_output)

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.

            self.dx = self.dlg.sb_dx.value()
            self.dy = self.dlg.sb_dy.value()

            self.out_shp = self.dlg.lineEdit.text()
            self.shpLayer = self.layers[self.dlg.cb_layers.currentText()]
            self.shpJson = [
                self.convertFeatToJson(feat)
                for feat in self.shpLayer.getFeatures()
            ]
            self.sr = self.shpLayer.crs().toWkt()
            self.jsonList = [
                self.move_feature(feat, self.dx, self.dy)
                for feat in self.shpJson
            ]

            if self.out_shp:
                try:
                    self.createShp(self.out_shp, self.jsonList, self.shpLayer,
                                   self.sr)
                    self.iface.messageBar().pushMessage(
                        "Success",
                        "Features moved successfully!",
                        level=Qgis.Success,
                        duration=5)
                except Exception as e:
                    self.iface.messageBar().pushMessage(
                        "Error",
                        "An error occured! Check the python console.",
                        level=Qgis.Critical,
                        duration=5)
                    print(e)
            else:
                try:
                    self.createShp(self.out_shp, self.jsonList, self.shpLayer,
                                   self.sr, "m")
                    self.iface.messageBar().pushMessage(
                        "Success",
                        "Features moved successfully!",
                        level=Qgis.Success,
                        duration=5)
                except Exception as e:
                    self.iface.messageBar().pushMessage(
                        "Error",
                        "An error occured! Check the python console.",
                        level=Qgis.Critical,
                        duration=5)
                    print(e)