class SimilarityPlugin: """ Similarity Plugin parent class .. Attributes ----------- dlg : SimilarityPluginDialog MainPluginDialog simpleDialog : SimpleWarningDialog Show simple warning similarLayer : list=[] The result of calculation process previewLayer: int=0 Current index similarLayer that previewed in canvas widget calcThread : QThread(self.iface) Thread for data processing calcTask : CalculationModule Calculation module for checking similarity iface : QgsInterface 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. """ layer: QgsVectorLayer #: First layer layer2: QgsVectorLayer #: Second layer simpleDialog: SimpleWarnDialog #: Simple warning dialog 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 """ self.similarLayer = [] """ """ self.previewLayer = 0 self.calcTask = CalculationModule() # Save reference to the QGIS interface self.iface = iface # Registering worker calculation self.calcThread = QThread(self.iface) self.calcTask.moveToThread(self.calcThread) self.calcThread.started.connect(self.calcTask.run) self.calcThread.setTerminationEnabled(True) # multithreading signal calculation self.calcTask.progress.connect(self.updateCalcProgress) self.calcTask.progressSim.connect(self.updateSimList) self.calcTask.finished.connect(self.finishedCalcThread) self.calcTask.error.connect(self.errorCalcThread) self.calcTask.eventTask.connect(self.eventCalcThread) # pan event self.actionPan = QAction("Pan", self.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', 'SimilarityPlugin_{}.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'&Calculate Similarity Map') # 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 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('SimilarityPlugin', 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/similarity_plugin/icon-24.png' self.add_action(icon_path, text=self.tr(u'Check Similarity ...'), 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'&Calculate Similarity Map'), action) self.iface.removeToolBarIcon(action) def methodChange(self): """Signal when method changed""" if self.dlg.methodComboBox.currentIndex() == 2: # self.dlg.mergeCenterCheck.setChecked(False) # self.dlg.setPKBtn.setVisible(True) self.dlg.mergeCenterCheck.setEnabled(True) self.dlg.lineEditTreshold.setEnabled(False) self.dlg.nnRadiusEdit.setEnabled(False) # self.pkSelector.layerListWidget.clear() # self.pkSelector.layerListWidget.addItems( # self.dlg.layerSel1.currentLayer().fields().names() # ) # self.pkSelector.layer2ListWidget.clear() # self.pkSelector.layer2ListWidget.addItems( # self.dlg.layerSel2.currentLayer().fields().names() # ) # self.pkSelector.open() elif self.dlg.methodComboBox.currentIndex() == 0: self.dlg.mergeCenterCheck.setChecked(False) self.dlg.mergeCenterCheck.setEnabled(False) # self.dlg.setPKBtn.setVisible(False) self.dlg.lineEditTreshold.setEnabled(True) self.dlg.nnRadiusEdit.setEnabled(False) elif self.dlg.methodComboBox.currentIndex() == 1: self.dlg.mergeCenterCheck.setChecked(True) self.dlg.mergeCenterCheck.setEnabled(False) # self.dlg.setPKBtn.setVisible(False) self.dlg.lineEditTreshold.setEnabled(True) self.dlg.nnRadiusEdit.setEnabled(True) def resultPreview(self): """Activate preview section This method will called if calculation process is finished See Also ---------- refreshPreview() SimilarityPluginDialog.widgetCanvas SimilarityPluginDialog.nextBtn SimilarityPluginDialog.previousBtn SimilarityPluginDialog.removeBtn """ self.previewLayer = 0 self.refreshPreview() self.dlg.widgetCanvas.enableAntiAliasing(True) self.dlg.nextBtn.setEnabled(True) self.dlg.previousBtn.setEnabled(True) self.dlg.removeBtn.setEnabled(True) def attrPrinter(self, fieldsList: object, feature: QgsFeature, place: QTextEdit): """print the attribute table on preview panel :param fieldsList object: List the attribute value of feature :param feature QgsFeature: The feature will be printed :param place QTextEdit: The place for editing text """ temp = '' for f in fieldsList: temp += f.name() temp += ' : ' temp += str(feature.attribute(f.name())) temp += '\n' # print(place) place.setText(temp) def refreshPreview(self): """refreshing canvas on preview""" if len(self.similarLayer) > 0: # set the layer self.layerCanvas = QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer', 'memory') self.layer2Canvas = QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer', 'memory') # set the feature previewLayerFeature = self.calcTask.getLayersDup()[0].getFeature( self.similarLayer[self.previewLayer][0]) previewLayerFeature2 = self.calcTask.getLayersDup()[1].getFeature( self.similarLayer[self.previewLayer][1]) # set the label score on preview scoreLabel = "Score : " + str( round(self.similarLayer[self.previewLayer][2], 3)) # set cumulative score on preview if (not self.calcTask.getTranslate()): scoreLabel += " - Cumulative score : " + str( self.calcTask.getCumulative( self.similarLayer[self.previewLayer])) # show distance if the layer merge centered (NN and WK only) if (self.dlg.methodComboBox.currentIndex() == 1 or self.dlg.methodComboBox.currentIndex() == 2): distance = QgsGeometry.distance( previewLayerFeature.geometry().centroid(), previewLayerFeature2.geometry().centroid()) if distance < 0.00001: distance = 0 self.dlg.labelScore.setText(scoreLabel + " - Distance : " + str(round(distance, 3))) else: self.dlg.labelScore.setText(scoreLabel) self.attrPrinter( self.calcTask.getLayersDup() [0].dataProvider().fields().toList(), previewLayerFeature, self.dlg.previewAttr) self.attrPrinter( self.calcTask.getLayersDup() [1].dataProvider().fields().toList(), previewLayerFeature2, self.dlg.previewAttr_2) self.layerCanvas.dataProvider().addFeature(previewLayerFeature) # translating preview if self.calcTask.getTranslate(): tGeom = self.calcTask.translateCenterGeom( previewLayerFeature2.geometry(), previewLayerFeature.geometry()) nFeat = QgsFeature(previewLayerFeature2) nFeat.setGeometry(tGeom) self.layer2Canvas.dataProvider().addFeature(nFeat) else: self.layer2Canvas.dataProvider().addFeature( previewLayerFeature2) # set canvas to preview feature layer self.dlg.widgetCanvas.setExtent( previewLayerFeature.geometry().boundingBox(), True) self.dlg.widgetCanvas.setDestinationCrs( self.layerCanvas.sourceCrs()) symbol = self.layerCanvas.renderer().symbol() symbol.setColor(QColor(0, 147, 221, 127)) symbol2 = self.layer2Canvas.renderer().symbol() symbol2.setColor(QColor(231, 120, 23, 127)) self.dlg.widgetCanvas.setLayers( [self.layer2Canvas, self.layerCanvas]) # redraw the canvas self.dlg.widgetCanvas.refresh() def nextPreview(self): """Next preview signal for next button in preview section""" # f2 = open("engine/f2.txt", "w") if (self.previewLayer < len(self.similarLayer) - 1): self.previewLayer = int(self.previewLayer) + 1 # self.dlg.consoleTextEdit.setText(self.dlg.consoleTextEdit.toPlainText()+"\n\n Current Similar Layer Index : \n "+str([self.similarLayer[self.previewLayer], self.previewLayer])) self.refreshPreview() def previousPreview(self): """Previous preview signal""" if (self.previewLayer > 0): self.previewLayer = int(self.previewLayer) - 1 # self.dlg.consoleTextEdit.setText(self.dlg.consoleTextEdit.toPlainText()+"\n\n Current Similar Layer Index : \n "+str([self.similarLayer[self.previewLayer], self.previewLayer])) self.refreshPreview() def rmFeatResult(self): """Removing similarity info current result""" self.similarLayer.pop(self.previewLayer) if (self.previewLayer > len(self.similarLayer)): self.previewLayer = len(self.similarLayer - 1) self.refreshPreview() self.warnDlg.close() def rmWarn(self): """prevention remove item preview""" self.warnDlg = self.warnDialogInit( 'Are you sure to delete this feature ?') self.warnDlg.yesBtn.clicked.connect(self.rmFeatResult) self.warnDlg.noBtn.clicked.connect(self.warnDlg.close) self.warnDlg.show() def updateCalcProgress(self, value): """Progress signal for calcTask""" self.dlg.progressBar.setValue(int(round(value, 1))) def updateSimList(self, simList: list): """Updating similiarity result signal""" self.similarLayer.append(simList) cText = "Number of Result: " + str(len(self.similarLayer)) self.dlg.counterLabel.setText(cText) # thread signal def errorCalcThread(self, value: str): """Signal when an error occured""" print("error : ", value) self.dlg.consoleTextEdit.append("error : " + value + "\n\n") self.simpleWarnDialogInit(value) self.dlg.calcBtn.setEnabled(True) self.dlg.stopBtn.setEnabled(False) def finishedCalcThread(self, itemVal: list): """signal when calcTask calculation is finished :param itemVal list: the returned value emit """ # print("finished returned : ", itemVal) # self.similarLayer = itemVal # self.setLayers(self.calcTask.getLayersDup()) self.calcThread.exit() self.calcTask.kill() cText = "Number of Result: " + str(len(self.similarLayer)) self.dlg.consoleTextEdit.append(cText + "\n\n") if len(self.similarLayer) > 0: # self.addScoreItem() self.dlg.consoleTextEdit.append( "The 2 vector layer has been checked\n\n") self.previewLayer = 0 self.dlg.saveBtn.setEnabled(True) self.dlg.counterLabel.setText(cText) self.resultPreview() else: self.previewLayer = 0 self.dlg.counterLabel.setText(cText) self.dlg.calcBtn.setEnabled(True) self.dlg.stopBtn.setEnabled(False) def stopCalcThread(self): """Signal when calcTask is stopped """ self.calcThread.exit() self.dlg.eventLabel.setText("Event: Stopped") self.calcTask.kill() if (self.calcTask.getLayersDup()[0].featureCount() > 0 and self.calcTask.getLayersDup()[1].featureCount() > 0): cText = "Number of Result: " + str(len(self.similarLayer)) self.dlg.consoleTextEdit.append(cText + "\n\n") if len(self.similarLayer) > 0: # self.addScoreItem() self.previewLayer = 0 self.dlg.saveBtn.setEnabled(True) self.dlg.counterLabel.setText(cText) self.resultPreview() else: self.previewLayer = 0 self.dlg.counterLabel.setText(cText) self.dlg.calcBtn.setEnabled(True) self.dlg.stopBtn.setEnabled(False) def eventCalcThread(self, value: str): """Receiving signal event :param value str: the returned value emit """ self.dlg.eventLabel.setText("Event: " + value) # executing calculation def calculateScore(self): """Signal for executing calculation for cheking maps""" if (isinstance(self.dlg.layerSel1.currentLayer(), QgsVectorLayer) and isinstance(self.dlg.layerSel1.currentLayer(), QgsVectorLayer)): # set plugin to initial condition self.dlg.progressBar.setValue(0) self.dlg.saveBtn.setEnabled(False) self.dlg.nextBtn.setEnabled(False) self.dlg.previousBtn.setEnabled(False) self.dlg.removeBtn.setEnabled(False) self.dlg.widgetCanvas.setLayers([ QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer', 'memory') ]) self.dlg.previewAttr.setText("") self.dlg.previewAttr_2.setText("") self.dlg.widgetCanvas.refresh() scoreLabel = "Score : 0" self.dlg.counterLabel.setText("Number of Result: 0") self.dlg.labelScore.setText(scoreLabel) self.similarLayer = [] # set input-output option self.calcTask.setLayers(self.dlg.layerSel1.currentLayer(), self.dlg.layerSel2.currentLayer()) self.calcTask.setTreshold(self.dlg.lineEditTreshold.value()) self.calcTask.setMethod(int( self.dlg.methodComboBox.currentIndex())) self.calcTask.setTranslate(self.dlg.mergeCenterCheck.isChecked()) self.calcTask.setRadius(self.dlg.nnRadiusEdit.value()) self.calcTask.setSuffix(str(self.dlg.sufLineEdit.text())) self.calcTask.setScoreName(str(self.dlg.attrOutLineEdit.text())) # print("input option set") # activating task self.calcTask.alive() # print("task alive") self.calcThread.start() # print("thread started") # set button self.dlg.calcBtn.setEnabled(False) self.dlg.stopBtn.setEnabled(True) else: # prevention on QgsVectorLayer only self.simpleWarnDialogInit("This plugin support Vector Layer only") # signal when saveBtn clicked def registerToProject(self): """Signal to registering project""" QgsProject.instance().addMapLayers(self.calcTask.getLayersResult()) # warning dialog for error or prevention def warnDialogInit(self, msg: str): """This dialog have Yes and No button. :param msg: str Display the warning message """ dialog = WarnDialog() #set the message dialog.msgLabel.setText(msg) return dialog # initializing simple warning dialog def simpleWarnDialogInit(self, msg: str): """ This dialog have ok button only :param: msg str: Display the warning message """ # Set the message self.simpleDialog.msgLabel.setText(msg) self.simpleDialog.show() # def pkSelectorAccepted(self): # if( len(self.pkSelector.layerListWidget.selectedItems()) > 0 and len(self.pkSelector.layer2ListWidget.selectedItems()) > 0 and # (len(self.pkSelector.layerListWidget.selectedItems()) == len(self.pkSelector.layer2ListWidget.selectedItems())) # ): # names = [j.text() for j in self.pkSelector.layerListWidget.selectedItems()] # names.sort() # names2 = [j.text() for j in self.pkSelector.layer2ListWidget.selectedItems()] # names2.sort() # print(names) # print(names2) # self.pkSelector.accept() # else: # self.simpleWarnDialogInit("Primary Key must be same in length as key or not null") def run(self): """Run method that performs all the real work""" # print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) # init run variable # self.canvas = QgsMapCanvas() # 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.previewLayer = 0 self.currentCheckLayer = [0, 0] self.first_start = False self.dlg = SimilarityPluginDialog() # self.dlg.setPKBtn.setVisible(False) self.simpleDialog = SimpleWarnDialog() # self.pkSelector = PkSelector() # set help documentation self.dlg.helpTextBrowser.load( QUrl( 'https://github.com/panickspa/SimilarityPlugin/wiki/User-Guide' )) self.dlg.nextHelpBtn.clicked.connect( self.dlg.helpTextBrowser.forward) self.dlg.previousHelpBtn.clicked.connect( self.dlg.helpTextBrowser.back) # filtering selection layer (empty layer not allowed) self.dlg.layerSel1.setAllowEmptyLayer(False) self.dlg.layerSel1.setAllowEmptyLayer(False) # self.pkSelector.okPushButton.clicked.connect(self.pkSelectorAccepted) # self.dlg.setPKBtn.clicked.connect(self.pkSelector.open) # method combobox initialiazation self.dlg.methodComboBox.clear() self.dlg.methodComboBox.addItems( ['Squential', 'Nearest Neightbour', 'Wilkerstat BPS']) # registering signal self.dlg.methodComboBox.currentIndexChanged.connect( self.methodChange) self.dlg.nextBtn.clicked.connect(self.nextPreview) self.dlg.previousBtn.clicked.connect(self.previousPreview) self.dlg.calcBtn.clicked.connect(self.calculateScore) self.dlg.saveBtn.clicked.connect(self.registerToProject) self.dlg.removeBtn.clicked.connect(self.rmWarn) self.dlg.stopBtn.clicked.connect(self.stopCalcThread) # intialize pan tool panTool = QgsMapToolPan(self.dlg.widgetCanvas) # set signal panTool.setAction(self.actionPan) # set map tool self.dlg.widgetCanvas.setMapTool(panTool) # set pan tool to be activate panTool.activate() # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: self.similarLayer = [] self.dlg.widgetCanvas.setLayers([ QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer', 'memory') ]) self.dlg.previewAttr.setText("") self.dlg.previewAttr_2.setText("") self.dlg.widgetCanvas.refresh() scoreLabel = "Score : 0" self.dlg.labelScore.setText(scoreLabel)