class featureLoader(object): def __init__(self, iface): self.iface = iface # 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', 'featureLoader_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) self.wkbText = {0:"GeometryUnknown", 1:"Point", 2:"LineString", 3:"Polygon", 4:"MultiPoint", 5:"MultiLineString", 6:"MultiPolygon", 7:"NoGeometry", 8:"Point25D", 9:"LineString25D", 10:"Polygon25D", 11:"MultiPoint25D", 12:"MultiLineString25D", 13:"MultiPolygon25D", 100:"NoGeometry"} #sometime qgis cannot return geometry type truly. this bug is handling in here self.geometryText = {0:"Point", 1:"LineString", 2:"Polygon", 3:"GeometryUnknown", 4:"NoGeometry"} # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Feature Loader') # TODO: We are going to let the user set this up in a future iteration self.toolbar = self.iface.addToolBar(u'featureLoader') self.toolbar.setObjectName(u'featureLoader') # 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('featureLoader', 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): 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: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu( 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/featureLoader/icon.png' self.add_action( icon_path, text=self.tr(u'Feature Loader'), callback=self.run, parent=self.iface.mainWindow()) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu( self.tr(u'&Feature Loader'), action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar def layerControl(self): if len(self.allVectorLayers) > 1: self.targetLayer = self.allVectorLayers[self.dlg.cmbTargetLayer.currentIndex()] sourceLayer = self.allVectorLayers[self.dlg.cmbSourceLayer.currentIndex()] openedAttrWindow = False #This variable is used for detecting opened attribute windows. During loading operation, opened windows make qgis crashed (maybe a bug) #so they must be closed for dialog in QApplication.instance().allWidgets(): #I noticed that in my laptop getting all Qt widget's (in QGIS) names give error. But no problem with desktop. Here is try-except ;) try: if dialog.objectName() in [u'QgsAttributeTableDialog', u'AttributeTable']: openedAttrWindow = True except: pass if openedAttrWindow: QMessageBox.warning(None,u'Notification', u'Please close all attribute windows to start the process.') else: #checking target and source layers must not be in editing mode. if not self.targetLayer.isEditable() and not sourceLayer.isEditable(): # checking for targetLayer editing capability. isEditable = self.targetLayer.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures if isEditable: # checking targetLayer and sourceLayer are not same if self.targetLayer.extent() != sourceLayer.extent() or self.targetLayer.publicSource() != sourceLayer.publicSource(): # checking layers geometry types if self.targetLayer.geometryType() == sourceLayer.geometryType(): self.loader = Loader(targetLayer=self.targetLayer,sourceLayer=sourceLayer) self.loader.setOptions(onlySelected=self.dlg.checkBox.isChecked()) self.dlg.btnStart.setEnabled(False) self.dlg.btnStop.setEnabled(True) self.dlg.btnStop.clicked.connect(self.loader.stop) self.iface.mapCanvas().setRenderFlag(False)#QGIS can not render dramatic changes in the target layer feature count and crashes down. So before starting we need to stop rendering. QObject.connect(self.loader, SIGNAL("progressLenght"), self.setProgressLength) QObject.connect(self.loader, SIGNAL("progress"), self.setProgress) QObject.connect(self.loader, SIGNAL("error"), self.error) QObject.connect(self.loader, SIGNAL("finished()"), self.done) QObject.connect(self.loader, SIGNAL('status'), self.setStatus) # QObject.connect(self.loader, SIGNAL('insertFeature'), self.insert) self.loader.start() self.start_time = timeit.default_timer()#for calculating total run time else: QMessageBox.warning(self.dlg, u'Error', u'The layers geometry types have to be same to start the process.') else: QMessageBox.warning(self.dlg, u'Error', u'Target Layer and Source Layer must be different.') else: QMessageBox.warning(self.dlg, u'Error', u'Target Layer does not support editing.') else: QMessageBox.warning(self.dlg, u'Error', u'Target Layer and Source Layer must not be in editing mode.') else: QMessageBox.warning(self.dlg, u'Error', u'There must be at least two vector layers added in QGIS canvas.') def setProgress(self, val): self.dlg.progressBar.setValue(val) def setProgressLength(self, val): self.dlg.progressBar.setMaximum(val) if val==0: self.dlg.btnStop.setEnabled(False)#this control may prevent errors when clicking stop button during saving changes. # def insert(self,feat): # self.targetLayer.startEditing() # self.targetLayer.addFeature(feat) def done(self): #this function is used by loader class's finished() signal. if not self.loader.hasError: if not self.loader.isCancel: # self.dlg.lblStatus self.committer = Committer(self.targetLayer)#this thread saves changes to datasource. QObject.connect(self.committer, SIGNAL("finished()"), lambda: self.commitFinished(self.targetLayer)) self.targetLayer.startEditing() self.targetLayer.addFeatures(self.loader.featureList, False) self.dlg.btnStop.setEnabled(False) QObject.connect(self.committer, SIGNAL('commitStarted'), self.commitStarted) self.committer.start() # self.resultGenerator(targetLayer.commitErrors()) else: self.resultGenerator(['Operation was canceled by user. All changes were rollbacked.']) self.onStop() else: self.onStop() def commitStarted(self): self.dlg.lblStatus.setText(u'Please wait while saving changes to the datasource...') self.dlg.progressBar.setMaximum(0) self.dlg.btnStop.setEnabled(False) def commitFinished(self,targetLayer): self.onStop() self.resultGenerator(targetLayer.commitErrors()) def error(self, exception): QMessageBox.critical(self.dlg, 'Error', str(exception) + ' All changes were rollbacked.') def onStop(self): self.dlg.progressBar.reset() self.dlg.progressBar.setMaximum(1) self.dlg.lblStatus.clear() self.loader.terminate() try: del self.loader del self.committer except: pass self.dlg.btnStart.setEnabled(True) self.dlg.btnStop.setEnabled(False) self.iface.mapCanvas().setRenderFlag(True) self.iface.mapCanvas().refresh() def resultGenerator(self, commitErrorList): self.resultDlg.textEdit.clear() total_run_time = '<p></p><b>Total execution time is %.2f seconds.</b><p></p>' % (timeit.default_timer()-self.start_time) self.resultDlg.textEdit.append(total_run_time) #First line is total tun time information for errorString in commitErrorList: self.resultDlg.textEdit.append(errorString) self.resultDlg.exec_() def setStatus(self,message): self.dlg.lblStatus.setText(message) def run(self): self.dlg = featureLoaderDialog() self.resultDlg = resultDialog() self.resultDlg.setFixedSize(self.resultDlg.size()) self.dlg.setFixedSize(self.dlg.size()) self.allVectorLayers = [] self.allMapLayers = QgsMapLayerRegistry.instance().mapLayers().items() for (notImportantForNow, layerObj) in self.allMapLayers: if layerObj.type() == 0:#0 is vectorlayer self.allVectorLayers.append(layerObj) if self.wkbText.has_key(layerObj.wkbType()):#Sometime qgis cannot return geometry type truly. This bug is handling in here cmbLabel = layerObj.name() + ' (%d) (%s)' % (layerObj.featureCount(), self.wkbText[layerObj.wkbType()]) else: cmbLabel = layerObj.name() + ' (%d) (%s)' % (layerObj.featureCount(), self.geometryText[layerObj.geometryType()]) self.dlg.cmbTargetLayer.addItem(cmbLabel) self.dlg.cmbSourceLayer.addItem(cmbLabel) self.dlg.btnStart.clicked.connect(self.layerControl) # if len(self.allVectorLayers) < 2: # self.dlg.btnStart.setEnabled(False) result = self.dlg.exec_() # Closing control if not result: try: self.loader.stop() self.committer.terminate() del self.loader del self.committer except: pass
class SpeedyLayer(object): def __init__(self, iface): self.iface = iface # 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', 'SpeedyLayer_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) #this variable is used as wkbType enum self.wkbText = {0:"GeometryUnknown", 1:"Point", 2:"LineString", 3:"Polygon", 4:"MultiPoint", 5:"MultiLineString", 6:"MultiPolygon", 7:"NoGeometry", 8:"Point25D", 9:"LineString25D", 10:"Polygon25D", 11:"MultiPoint25D", 12:"MultiLineString25D", 13:"MultiPolygon25D", 100:"NoGeometry"} #sometime qgis cannot return geometry type truly. this bug is handling in here self.geometryText = {0:"Point", 1:"LineString", 2:"Polygon", 3:"GeometryUnknown", 4:"NoGeometry"} #this variable is useful for determining QVariant types as string self.QVariant_Dict = {} for i in QVariant.__dict__: if str(QVariant.__dict__[i])[0] != '<' and i[0] != '_': self.QVariant_Dict[QVariant.__dict__[i]] = i # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Speedy Layer') # TODO: We are going to let the user set this up in a future iteration self.toolbar = self.iface.addToolBar(u'SpeedyLayer') self.toolbar.setObjectName(u'SpeedyLayer') # 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('SpeedyLayer', 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: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu( self.menu, action) self.actions.append(action) return action def initGui(self): """Generate the context menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/SpeedyLayer/icon.png' self.add_action( icon_path, text=self.tr(u'Speedy Layer'), callback=self.run, parent=self.iface.mainWindow()) #add contextual menu self.dup_to_memory_layer_action = QAction(QIcon(icon_path), "Duplicate to memory layer", self.iface.legendInterface() ) self.iface.legendInterface().addLegendLayerAction(self.dup_to_memory_layer_action, "","01", QgsMapLayer.VectorLayer,True) self.dup_to_memory_layer_action.triggered.connect(self.run) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu( self.tr(u'&Speedy Layer'), action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar # remove contextual menu self.iface.legendInterface().removeLegendLayerAction(self.dup_to_memory_layer_action) def process(self): if len(self.allVectorLayers) > 0: self.vectorLayer = self.allVectorLayers[self.dlg.cmbVectorLayer.currentIndex()] self.memoryLayer = self.generateMemoryLayer(self.vectorLayer,self.selectedLayerFields) self.loader = Loader(targetLayer=self.memoryLayer,sourceLayer=self.vectorLayer) self.loader.setOptions(onlySelected=self.dlg.checkBox.isChecked()) self.dlg.btnStart.setEnabled(False) self.dlg.btnStop.setEnabled(True) self.dlg.btnStop.clicked.connect(self.loader.stop) self.iface.mapCanvas().setRenderFlag(False)#QGIS can not render dramatic changes in the target layer feature count and crashes down. So before starting we need to stop rendering. QObject.connect(self.loader, SIGNAL("progressLenght"), self.setProgressLength) QObject.connect(self.loader, SIGNAL("progress"), self.setProgress) QObject.connect(self.loader, SIGNAL("error"), self.error) QObject.connect(self.loader, SIGNAL("finished()"), self.done) QObject.connect(self.loader, SIGNAL('status'), self.setStatus) # QObject.connect(self.loader, SIGNAL('debug'), self.debug) self.loader.start() else: QMessageBox.warning(self.dlg, u'Error', u'There must be at least one vector layers added in QGIS canvas.') # def debug(self,msg): # QMessageBox.information(None,'debug',msg) def setProgress(self, val): self.dlg.progressBar.setValue(val) def setProgressLength(self, val): self.dlg.progressBar.setMaximum(val) if val==0: self.dlg.btnStop.setEnabled(False)#this control may prevent errors when clicking stop button during saving changes. def error(self, exception): QMessageBox.critical(self.dlg, 'Error', str(exception) + '. All changes were rollbacked.') def done(self): #this function is used by loader class's finished() signal. if not self.loader.hasError: if not self.loader.isCancel: QgsMapLayerRegistry.instance().addMapLayer(self.memoryLayer) self.committer = Committer(self.memoryLayer)#this thread saves changes to datasource. QObject.connect(self.committer, SIGNAL("finished()"), self.commitFinished) QObject.connect(self.committer, SIGNAL('commitStarted'), self.commitStarted) self.memoryLayer.startEditing() self.dlg.btnStop.setEnabled(False) self.memoryLayer.addFeatures(self.loader.featureList, False) self.committer.start() else: QMessageBox.information(self.dlg,'Result','Operation was canceled by user') self.onStop() else: self.onStop() def commitStarted(self): self.dlg.lblStatus.setText(u'Please wait while memory allocation for new features...') self.dlg.progressBar.setMaximum(0) self.dlg.btnStop.setEnabled(False) def commitFinished(self): self.onStop() QMessageBox.information(self.dlg,'Result', "Selected layer was copied to memory and added to the Canvas") self.dlg.hide() def onStop(self): self.dlg.progressBar.setMaximum(1) self.dlg.progressBar.reset() self.dlg.lblStatus.clear() self.loader.terminate() try: del self.loader del self.committer except: pass self.dlg.btnStart.setEnabled(True) self.dlg.btnStop.setEnabled(False) self.iface.mapCanvas().setRenderFlag(True) self.iface.mapCanvas().refresh() def setStatus(self,message): self.dlg.lblStatus.setText(message) def generateMemoryLayer(self, targetLayer, fieldDict): epsg = 'EPSG:'+ str(targetLayer.crs().postgisSrid()) if self.geometryText.has_key(targetLayer.wkbType()): geometry = self.wkbText[targetLayer.wkbType()] else: geometry = self.geometryText[targetLayer.geometryType()] name = targetLayer.name() memoryLayer = QgsVectorLayer(geometry + '?crs=' + epsg, name, "memory") memoryLayer.startEditing()#to add field it needs to be in editing mode for fieldName in fieldDict: field = QgsField(fieldName, fieldDict[fieldName]) memoryLayer.dataProvider().addAttributes([field]) memoryLayer.updateFields() memoryLayer.commitChanges() return memoryLayer def listFields(self): #this function fills field comboboxes self.selectedLayerFields = OrderedDict() #holds fieldname:type of all fields in selected layer self.dlg.lstFields.clear() #clear if self.allVectorLayers: targetVectorLayer = self.allVectorLayers[self.dlg.cmbVectorLayer.currentIndex()] attributes = targetVectorLayer.dataProvider().fields().toList() for attribute in attributes: self.selectedLayerFields[attribute.name()] = attribute.type() self.dlg.lstFields.addItem(attribute.name() + ' (%s)' %(self.QVariant_Dict[attribute.type()])) def removeField(self): selectedFieldList = self.dlg.lstFields.selectedItems() #QWidgetItem List for field in selectedFieldList: index = self.dlg.lstFields.indexFromItem(field).row() fieldName = field.text()[:field.text().index(' (')] del self.selectedLayerFields[fieldName] #removing not only from listWidget but also from this dict self.dlg.lstFields.takeItem(index) def run(self): self.dlg = SpeedyLayerDialog() self.dlg.setFixedSize(self.dlg.size()) self.allVectorLayers = [] self.allMapLayers = QgsMapLayerRegistry.instance().mapLayers().items() for (notImportantForNow, layerObj) in self.allMapLayers: if layerObj.type() == 0:#0 is vectorlayer self.allVectorLayers.append(layerObj) if self.wkbText.has_key(layerObj.wkbType()):#Sometime qgis cannot return geomerty type truly. This bug is handling in here cmbLabel = layerObj.name() + ' (%d) (%s)' % (layerObj.featureCount(), self.wkbText[layerObj.wkbType()]) else: cmbLabel = layerObj.name() + ' (%d) (%s)' % (layerObj.featureCount(), self.geometryText[layerObj.geometryType()]) self.dlg.cmbVectorLayer.addItem(cmbLabel,layerObj.id()) self.selectedLayerFields = {} #this dict holds selected fields name:QVariant.Type of target layer # pre-select current layer if selected in legend interface if self.iface.legendInterface().currentLayer(): current_idx = self.dlg.cmbVectorLayer.findData(self.iface.legendInterface().currentLayer().id()) if current_idx != -1: self.dlg.cmbVectorLayer.setCurrentIndex(current_idx) self.listFields() self.dlg.cmbVectorLayer.currentIndexChanged.connect(self.listFields) self.dlg.btnRemove.clicked.connect(self.removeField) self.dlg.btnReset.clicked.connect(self.listFields) self.dlg.btnStart.clicked.connect(self.process) self.dlg.show() result = self.dlg.exec_() # Closing control if not result: try: self.loader.stop() self.committer.terminate() del self.loader del self.committer except: pass