def uploadDoc(self): """ Upload the file to the central repository in a separate thread using the specified file manager """ if isinstance(self.fileManager, NetworkFileManager): self.pgBar.setVisible(True) self._docSize = self.fileInfo.size() ''' Create document transfer helper for multi-threading capabilities. Use of queued connections will guarantee that signals and slots are captured in any thread. ''' workerThread = QThread(self) docWorker = DocumentTransferWorker( self.fileManager, self.fileInfo, "%s" % (self._source_entity), "%s" % (self._doc_type), self ) docWorker.moveToThread(workerThread) workerThread.started.connect(docWorker.transfer) docWorker.blockWrite.connect(self.onBlockWritten) docWorker.complete.connect(self.onCompleteTransfer) workerThread.finished.connect(docWorker.deleteLater) workerThread.finished.connect(workerThread.deleteLater) workerThread.start() # Call transfer() to get fileUUID early # docWorker.transfer() self.fileUUID = docWorker.file_uuid
def runSearch(self): '''Called when the user pushes the Search button''' selectedLayer = self.layerListComboBox.currentIndex() comparisonMode = self.comparisonComboBox.currentIndex() self.noSelection = True try: sstr = self.findStringEdit.text().strip() except: self.showErrorMessage('Invalid Search String') return if str == '': self.showErrorMessage('Search string is empty') return if selectedLayer == 0: # Include all vector layers layers = QgsProject.instance().mapLayers().values() elif selectedLayer == 1: # Include all selected vector layers layers = self.iface.layerTreeView().selectedLayers() else: # Only search on the selected vector layer layers = [self.searchLayers[selectedLayer]] self.vlayers = [] # Find the vector layers that are to be searched for layer in layers: if isinstance(layer, QgsVectorLayer): self.vlayers.append(layer) if len(self.vlayers) == 0: self.showErrorMessage( 'There are no vector layers to search through') return # vlayers contains the layers that we will search in self.searchButton.setEnabled(False) self.stopButton.setEnabled(True) self.doneButton.setEnabled(False) self.clearButton.setEnabled(False) self.clearResults() self.resultsLabel.setText('') infield = self.searchFieldComboBox.currentIndex() >= 1 if infield is True: selectedField = self.searchFieldComboBox.currentText() else: selectedField = None # Because this could take a lot of time, set up a separate thread # for a worker function to do the searching. thread = QThread() worker = Worker(self.vlayers, infield, sstr, comparisonMode, selectedField, self.maxResults) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(self.workerFinished) worker.foundmatch.connect(self.addFoundItem) worker.error.connect(self.workerError) self.thread = thread self.worker = worker self.noSelection = False thread.start()
def start_computing(self): """computing called in new thread and showing a progressBar""" computation = Move(self.input.text(), self.output.text(), self.ellipsoidSelector.text(), self.units.currentText(), self.value.text()) messageBar = self.iface.messageBar().createMessage('Computing...') progressBar = QProgressBar() cancelButton = QPushButton() cancelButton.setText('Cancel') messageBar.layout().addWidget(progressBar) messageBar.layout().addWidget(cancelButton) computationThread = QThread(self) computation.moveToThread(computationThread) computation.finished.connect(self.computing_finished) computation.progress.connect(progressBar.setValue) cancelButton.clicked.connect(self.cancel_computing) computationThread.started.connect(computation.shift) computation.error.connect(self.value_error) self.iface.messageBar().pushWidget(messageBar, Qgis.Info) if not computationThread.isRunning(): self.computationRunning = True computationThread.start() self.computation = computation self.computationThread = computationThread self.messageBar = messageBar
def startWorker(self): self.dlg.segmentingProgress.reset() self.settings = self.dlg.get_settings() if self.settings['output_type'] == 'postgis': db_settings = self.dlg.get_dbsettings() self.settings.update(db_settings) if lfh.getLayerByName(self.settings['input']).crs().postgisSrid() == 4326: self.giveMessage('Re-project the layer. EPSG:4326 not allowed.', Qgis.Info) elif self.settings['output'] != '': segmenting = self.Worker(self.settings, self.iface) self.dlg.lockGUI(True) # start the segmenting in a new thread thread = QThread() self.thread_error = '' segmenting.moveToThread(thread) segmenting.finished.connect(self.workerFinished) segmenting.error.connect(self.workerError) segmenting.warning.connect(self.giveMessage) segmenting.segm_progress.connect(self.dlg.segmentingProgress.setValue) thread.started.connect(segmenting.run) thread.start() self.thread = thread self.segmenting = segmenting else: self.giveMessage('Missing user input!', Qgis.Info) return
def runSearch(self): '''Called when the user pushes the Search button''' selectedLayer = self.layerListComboBox.currentIndex() comparisonMode = self.comparisonComboBox.currentIndex() self.noSelection = True try: sstr = self.findStringEdit.text().strip() except: self.showErrorMessage('Invalid Search String') return if str == '': self.showErrorMessage('Search string is empty') return if selectedLayer == 0: # Include all vector layers layers = QgsProject.instance().mapLayers().values() elif selectedLayer == 1: # Include all selected vector layers layers = self.iface.layerTreeView().selectedLayers() else: # Only search on the selected vector layer layers = [self.searchLayers[selectedLayer]] self.vlayers=[] # Find the vector layers that are to be searched for layer in layers: if isinstance(layer, QgsVectorLayer): self.vlayers.append(layer) if len(self.vlayers) == 0: self.showErrorMessage('There are no vector layers to search through') return # vlayers contains the layers that we will search in self.searchButton.setEnabled(False) self.stopButton.setEnabled(True) self.doneButton.setEnabled(False) self.clearButton.setEnabled(False) self.clearResults() self.resultsLabel.setText('') infield = self.searchFieldComboBox.currentIndex() >= 1 if infield is True: selectedField = self.searchFieldComboBox.currentText() else: selectedField = None # Because this could take a lot of time, set up a separate thread # for a worker function to do the searching. thread = QThread() worker = Worker(self.vlayers, infield, sstr, comparisonMode, selectedField, self.maxResults) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(self.workerFinished) worker.foundmatch.connect(self.addFoundItem) worker.error.connect(self.workerError) self.thread = thread self.worker = worker self.noSelection = False thread.start()
def runSearch(self): '''Called when the user pushes the Search button''' # selectedLayer = self.layerListComboBox.currentIndex() self.updateTableStructure(",".join(self.display_field_list_temp)) selectedLayer = self.layerListComboBox.currentText() comparisonMode = self.comparisonComboBox.currentIndex() self.noSelection = True try: sstr = self.findStringEdit.text().strip() except: self.showErrorMessage('Invalid Search String') return type_index=self.chooseType.currentIndex() # Only search on the selected vector layer layers = [self.searchLayers[selectedLayer]] self.vlayers=[] # Find the vector layers that are to be searched for layer in layers: if isinstance(layer, QgsVectorLayer): self.vlayers.append(layer) if len(self.vlayers) == 0: self.showErrorMessage('Please add respective layers') return # vlayers contains the layers that we will search in self.searchButton.setEnabled(False) self.stopButton.setEnabled(True) self.doneButton.setEnabled(False) self.clearButton.setEnabled(False) self.clearResults() self.resultsLabel.setText('') selectedField = self.searchFieldComboBox.currentText() if 'all field' in selectedField.lower() or 'all fields' in selectedField.lower(): infield = self.searchFieldComboBox.currentIndex() >= 1 else: infield = self.searchFieldComboBox.currentIndex() >= 0 # Because this could take a lot of time, set up a separate thread # for a worker function to do the searching. thread = QThread() worker = Worker(self.vlayers, infield, sstr, comparisonMode, selectedField, self.maxResults) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(self.workerFinished) worker.foundmatch.connect(self.addFoundItem) worker.error.connect(self.workerError) self.thread = thread self.worker = worker self.noSelection = False thread.start()
def runSearch(self): '''Called when the user pushes the Search button''' searchT = str(self.searchTypeComboBox.currentText()) searchP = self.plugin.searchTypes[searchT][0] selectedField = self.plugin.searchTypes[searchT][1] infield = selectedField != "*" comparisonMode = self.comparisonComboBox.currentIndex() self.noSelection = True try: sstr = self.findStringEdit.text().strip() except: self.showErrorMessage(self.tr(u'Invalid Search String')) return if sstr == '': self.showErrorMessage(self.tr(u'Search string is empty')) return # the vector layers that are to be searched self.vlayers = [] # find layer by path or name for lay in self.iface.mapCanvas().layers(): lp = lay.dataProvider().dataSourceUri().split('|')[0] if lp in searchP or lay.name() in searchP: self.vlayers.append(lay) # layers found? if len(self.vlayers) == 0: self.showErrorMessage( self. tr(u'There are no open/visible vector layers to search through' )) self.showErrorMessage( self.tr(u'Add one of the following layer to your project: ') + str(searchP)) return # vlayers contains the layers that we will search in self.searchButton.setEnabled(False) self.stopButton.setEnabled(True) self.doneButton.setEnabled(False) self.clearButton.setEnabled(False) self.clearResults() self.resultsLabel.setText('') # Because this could take a lot of time, set up a separate thread # for a worker function to do the searching. thread = QThread() worker = Worker(self.vlayers, infield, sstr, comparisonMode, selectedField, self.maxResults) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(self.workerFinished) worker.foundmatch.connect(self.addFoundItem) worker.error.connect(self.workerError) self.thread = thread self.worker = worker self.noSelection = False thread.start()
def startWorker(self): self.dlg.cleaningProgress.reset() self.settings = self.dlg.get_settings() if self.settings['output_type'] == 'postgis': db_settings = self.dlg.get_dbsettings() if 'dbname' not in db_settings: self.giveMessage( 'Please make sure a database is set for exporting to PostGIS', Qgis.Info) return if 'schema' not in db_settings: self.giveMessage( 'Please make sure a schema is set for exporting to PostGIS', Qgis.Info) return if 'table_name' not in db_settings: self.giveMessage( 'Please make sure a table is set for exporting to PostGIS', Qgis.Info) return self.settings.update(db_settings) if lfh.getLayerByName( self.settings['input']).crs().postgisSrid() == 4326: self.giveMessage('Re-project the layer. EPSG:4326 not allowed.', Qgis.Info) return elif self.settings['output'] != '': cleaning = self.Worker(self.settings, self.iface) # start the cleaning in a new thread self.dlg.lockGUI(True) self.dlg.lockSettingsGUI(True) thread = QThread() self.thread_error = '' cleaning.moveToThread(thread) cleaning.finished.connect(self.workerFinished) cleaning.error.connect(self.workerError) cleaning.warning.connect(self.giveMessage) cleaning.cl_progress.connect(self.dlg.cleaningProgress.setValue) thread.started.connect(cleaning.run) thread.start() self.thread = thread self.cleaning = cleaning if is_debug: print('started') else: self.giveMessage('Missing user input!', Qgis.Info) return
def start_gnss(self): TOMsMessageLog.logMessage("In gnss_tool:start_gnss - GPS port is specified ", level=Qgis.Info) if self.gpsPort: self.gpsAvailable = True self.gpsConnection = None self.curr_gps_location = None self.curr_gps_info = None self.gps_thread = GPS_Thread(self.dest_crs, self.gpsPort) thread = QThread() self.gps_thread.moveToThread(thread) self.gps_thread.gpsActivated.connect(self.gpsStarted) self.gps_thread.gpsPosition.connect(self.gpsPositionProvided) self.gps_thread.gpsDeactivated.connect(self.gpsStopped) #self.gps_thread.gpsError.connect(self.gpsErrorEncountered) #objThread = QThread() #obj = SomeObject() #obj.moveToThread(objThread) #self.gps_thread.gpsDeactivated.connect(thread.quit) #obj.finished.connect(objThread.quit) #self.gps_thread.started.connect(self.startGPS) #objThread.started.connect(obj.long_running) #thread.finished.connect(self.gpsStopped) #objThread.finished.connect(app.exit) #thread.start() #objThread.start() # self.gps_thread.progress.connect(progressBar.setValue) thread.started.connect(self.gps_thread.startGPS) # thread.finished.connect(functools.partial(self.gpsStopped, thread)) thread.start() self.thread = thread TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems - attempting connection ", level=Qgis.Info)
class BackgroundLayerDownload(QObject): finished = pyqtSignal('PyQt_PyObject', str) def __init__(self, server, user, repo, layername, commitid, filepath, extent): QObject.__init__(self) self.server = server self.user = user self.repo = repo self.layername = layername self.commitid = commitid self.filepath = filepath self.extent = extent self.nfeaturesRead = 0 self.actualLayerName = None # this puts the LayerGetter in its own thread self.worker = QThread() self.layer_getter = LayerGetter(server, user, repo, layername, commitid, filepath, extent) self.worker.started.connect(self.layer_getter.start) self.layer_getter.moveToThread(self.worker) self.layer_getter.completed.connect(self.completed) self.layer_getter.progress_occurred.connect(self.featuresRead) def start(self): startProgressBar("Downloading from GeoGig server", 0, currentWindow().messageBar()) self.worker.start() # start load # called (on main thread) when the work is done def completed(self): self.worker.quit() # get rid of worker thread closeProgressBar() self.actualLayerName = self.layer_getter.actualLayerName layer = QgsVectorLayer( self.filepath + "|layername=" + self.actualLayerName, self.actualLayerName) self.finished.emit(layer, self.filepath) # called as feature are read def featuresRead(self, nfeatsBatch): self.nfeaturesRead += nfeatsBatch setProgressText("Downloaded " + "{:,}".format(self.nfeaturesRead) + " features...")
def start_worker(worker, message_bar, message): """ Configure the QgsMessageBar with a :guilabel:`Cancel` button and start the worker in a new thread :param worker: the worker to be started :param message_bar: the message bar to be used to display progress :param message: a message describing the task to be performed """ # configure the QgsMessageBar message_bar_item = message_bar.createMessage(message) progress_bar = QProgressBar() progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) cancel_button = QPushButton() cancel_button.setText('Cancel') cancel_button.clicked.connect(worker.kill) message_bar_item.layout().addWidget(progress_bar) message_bar_item.layout().addWidget(cancel_button) message_bar.pushWidget(message_bar_item, message_bar.INFO) # start the worker in a new thread thread = QThread(message_bar.parent()) worker.moveToThread(thread) worker.set_message.connect( lambda message: set_worker_message(message, message_bar_item)) worker.toggle_show_progress.connect( lambda show: toggle_worker_progress(show, progress_bar)) worker.toggle_show_cancel.connect( lambda show: toggle_worker_cancel(show, cancel_button)) worker.finished.connect(lambda result: worker_finished( result, thread, worker, message_bar, message_bar_item)) worker.error.connect( lambda e, exception_str: worker_error(e, exception_str, message_bar)) worker.progress.connect(progress_bar.setValue) thread.started.connect(worker.run) thread.start() return thread, message_bar_item
def loadAsync(self): """ Asynchronously loads an entity's attribute values. """ self.asyncStarted.emit() # Create model worker workerThread = QThread(self) modelWorker = ModelWorker() modelWorker.moveToThread(workerThread) # Connect signals modelWorker.error.connect(self.errorHandler) workerThread.started.connect(lambda: modelWorker.fetch( self.config.STRModel, self.currentFieldName())) modelWorker.retrieved.connect(self._asyncFinished) modelWorker.retrieved.connect(workerThread.quit) workerThread.finished.connect(modelWorker.deleteLater) workerThread.finished.connect(workerThread.deleteLater) # Start thread workerThread.start()
def runAnalysis(self): self.dlg.analysisProgress.reset() # Create an analysis instance self.settings = self.getAnalysisSettings() if self.settings != {} and self.settings is not None: analysis = ca.CatchmentAnalysis(self.iface, self.settings) # Create new thread and move the analysis class to it self.dlg.lockGUI(True) analysis_thread = QThread() analysis.moveToThread(analysis_thread) # Setup signals analysis.finished.connect(self.analysisFinish) analysis.error.connect(self.analysisError) analysis.warning.connect(self.giveWarningMessage) analysis.progress.connect(self.dlg.analysisProgress.setValue) # Start analysis analysis_thread.started.connect(analysis.analysis) analysis_thread.start() self.analysis_thread = analysis_thread self.analysis = analysis
def start_worker(worker, iface, message, with_progress=True): # configure the QgsMessageBar message_bar_item = iface.messageBar().createMessage(message) progress_bar = QProgressBar() progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) if not with_progress: progress_bar.setMinimum(0) progress_bar.setMaximum(0) cancel_button = QPushButton() cancel_button.setText('Cancel') cancel_button.clicked.connect(worker.kill) message_bar_item.layout().addWidget(progress_bar) message_bar_item.layout().addWidget(cancel_button) iface.messageBar().pushWidget(message_bar_item, Qgis.Info) # start the worker in a new thread # let Qt take ownership of the QThread thread = QThread(iface.mainWindow()) worker.moveToThread(thread) worker.set_message.connect(lambda message: set_worker_message( message, message_bar_item)) worker.toggle_show_progress.connect(lambda show: toggle_worker_progress( show, progress_bar)) worker.toggle_show_cancel.connect(lambda show: toggle_worker_cancel( show, cancel_button)) worker.finished.connect(lambda result: worker_finished( result, thread, worker, iface, message_bar_item)) worker.error.connect(lambda e: worker_error(e)) worker.was_killed.connect(lambda result: worker_killed( result, thread, worker, iface, message_bar_item)) worker.progress.connect(progress_bar.setValue) thread.started.connect(worker.run) thread.start() return thread, message_bar_item
class ClipLayersDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" super(ClipLayersDialog, self).__init__(parent) # Set up the user interface from Designer through FORM_CLASS. # After self.setupUi() you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) self.iface = iface self.settings = QgsSettings("Hideto Yonezawa", "cliplayerss") # ExtentGroupBoxにMapCanvasを設定 self.mExtentGroupBox.setMapCanvas(self.iface.mapCanvas()) self.mExtentGroupBox.setOutputExtentFromCurrent() # 常にJGD2000で表示 self.mExtentGroupBox.setOutputCrs( QgsCoordinateReferenceSystem('EPSG:4612')) self.mExtentGroupBox.findChild( QPushButton, "mButtonCalcFromLayer").setEnabled(False) self.mExtentGroupBox.findChild(QPushButton, "mButtonDrawOnCanvas").setEnabled(False) # QgsMapCanvas のシグナルに接続 canvas = self.iface.mapCanvas() canvas.extentsChanged.connect( self.mExtentGroupBox.setOutputExtentFromCurrent) self.mExportDBNameQgsFileWidget.setStorageMode(QgsFileWidget.SaveFile) self.mExportDBNameQgsFileWidget.setConfirmOverwrite(True) self.mExportDBNameQgsFileWidget.setDialogTitle( self.tr("エクスポート先のGeoPackageファイル名を指定する")) self.mExportDBNameQgsFileWidget.setDefaultRoot( self.settings.value("lastGpkgDirectory", QgsProject.instance().homePath(), str)) self.mExportDBNameQgsFileWidget.setFilter( self.tr("GeoPackege (*.gpkg *.GPKG)")) self.mExportDBNameQgsFileWidget.fileChanged.connect( self.updateLastGpkgPath) self.mExportProjectNameQgsFileWidget.setStorageMode( QgsFileWidget.SaveFile) self.mExportProjectNameQgsFileWidget.setConfirmOverwrite(True) self.mExportProjectNameQgsFileWidget.setDialogTitle( self.tr("エクスポート先のプロジェクトファイル名を指定する")) self.mExportProjectNameQgsFileWidget.setDefaultRoot( self.settings.value("lastQGSProjectDirectory", QgsProject.instance().homePath(), str)) self.mExportProjectNameQgsFileWidget.setFilter( self.tr("QGIS Projectfile (*.qgs *.QGS)")) self.mExportProjectNameQgsFileWidget.fileChanged.connect( self.updateLastQGSProjectPath) self.thread = QThread() self.exporter = ClipByExtent() self.btnOk = self.mDlgButton_box.button(QDialogButtonBox.Ok) self.btnCancel = self.mDlgButton_box.button(QDialogButtonBox.Cancel) self.exporter.exportError.connect(self.exportCanceled) self.exporter.exportMessage.connect(self.logMessage) self.exporter.exportFinished.connect(self.exportCompleted) self.exporter.exportProcessed.connect(self.updateProgress) # デバッグするときはスレッドを使わないほうがデバッグしやすい self.exporter.moveToThread(self.thread) self.exporter.exportError.connect(self.thread.quit) self.exporter.exportFinished.connect(self.thread.quit) self.thread.started.connect(self.exporter.clipAndExport) self.mMinMaxClearCheckBox.setChecked( self.settings.value("minmaxclear", True, bool)) def closeEvent(self, event): self._saveSettings() self._restoreGui() QDialog.closeEvent(self, event) def updateLastGpkgPath(self, gpkgPath): self.mExportDBNameQgsFileWidget.setDefaultRoot(gpkgPath) self.settings.setValue("lastGpkgDirectory", os.path.dirname(gpkgPath)) def updateLastQGSProjectPath(self, projectpath): self.mExportProjectNameQgsFileWidget.setDefaultRoot(projectpath) self.settings.setValue("lastQGSProjectDirectory", os.path.dirname(projectpath)) def reject(self): self._saveSettings() self._restoreGui() QDialog.reject(self) def accept(self): self._saveSettings() gpkgName = self.mExportDBNameQgsFileWidget.filePath() if gpkgName == '': self.iface.messageBar().pushWarning( self.tr("出力先ファイル名未指定"), self.tr("出力先のファイル名がセットされていません。出力先ファイル名をセットしてください。")) return qgsName = self.mExportProjectNameQgsFileWidget.filePath() if qgsName == "": self.iface.messageBar().pushWarning( self.tr("出力先QGISプロジェクトファイル未指定"), self. tr("出力先QGISプロジェクトファイル名がセットされていません。出力先QGISプロジェクトファイル名をセットしてください。" )) return self.exporter.setOutputGpkgPath(gpkgName) self.exporter.setOutputProjectPath(qgsName) self.exporter.setMinMaxClear(self.mMinMaxClearCheckBox.isChecked()) # 表示エリアのエクステント以外ははじく ユーザ定義(直接入力)もはじく if self.mExtentGroupBox.extentState( ) != self.mExtentGroupBox.ExtentState.CurrentExtent: self.iface.messageBar().pushWarning( self.tr("エクステント指定"), self.tr("エクステント指定が不正です。キャンバスの領域を指定してください。")) return self.exporter.setExtent(self.mExtentGroupBox.outputExtent()) self.btnOk.setEnabled(False) self.btnCancel.setEnabled(False) # デバッグするときはスレッドを使わないほうがデバッグしやすい self.thread.start() #self.exporter.clipAndExport() def updateProgress(self, value): self.mProgressBar.setValue(value) def logMessage(self, message, level=Qgis.Info): QgsMessageLog.logMessage(message, "cliplayers", level) self.mLogplainTextEdit.appendPlainText(message) def exportCanceled(self, message): self.iface.messageBar().pushWarning(self.tr("Export error"), message) self._restoreGui() def exportCompleted(self): self.iface.messageBar().pushSuccess(self.tr("Export completed"), self.tr("Export sucessfully.")) #QDialog.accept(self) self.btnCancel.setEnabled(True) def _restoreGui(self): self.mProgressBar.setValue(0) self.btnOk.setEnabled(True) self.btnCancel.setEnabled(True) self.mLogplainTextEdit.clear() def _saveSettings(self): self.settings.setValue("minmaxclear", self.mMinMaxClearCheckBox.isChecked())
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)
def enableFeaturesWithGPSToolbarItems(self): TOMsMessageLog.logMessage("In enablefeaturesWithGPSToolbarItems", level=Qgis.Warning) self.gpsAvailable = False self.closeTOMs = False self.tableNames = TOMsLayers(self.iface) self.params = gpsParams() self.tableNames.TOMsLayersNotFound.connect(self.setCloseTOMsFlag) #self.tableNames.gpsLayersNotFound.connect(self.setCloseCaptureGPSFeaturesFlag) self.params.TOMsParamsNotFound.connect(self.setCloseCaptureGPSFeaturesFlag) self.TOMsConfigFileObject = TOMsConfigFile() self.TOMsConfigFileObject.TOMsConfigFileNotFound.connect(self.setCloseTOMsFlag) self.TOMsConfigFileObject.initialiseTOMsConfigFile() self.tableNames.getLayers(self.TOMsConfigFileObject) self.prj = QgsProject().instance() self.dest_crs = self.prj.crs() TOMsMessageLog.logMessage("In captureGPSFeatures::init project CRS is " + self.dest_crs.description(), level=Qgis.Warning) self.transformation = QgsCoordinateTransform(QgsCoordinateReferenceSystem("EPSG:4326"), self.dest_crs, self.prj) self.params.getParams() if self.closeTOMs: QMessageBox.information(self.iface.mainWindow(), "ERROR", ("Unable to start editing tool ...")) #self.actionProposalsPanel.setChecked(False) return # TODO: allow function to continue without GPS enabled ... # Now check to see if the port is set. If not assume that just normal tools gpsPort = self.params.setParam("gpsPort") TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems: GPS port is: {}".format(gpsPort), level=Qgis.Warning) self.gpsConnection = None if gpsPort: self.gpsAvailable = True if self.gpsAvailable == True: self.curr_gps_location = None self.curr_gps_info = None TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems - GPS port is specified ", level=Qgis.Info) self.gps_thread = GPS_Thread(self.dest_crs, gpsPort) thread = QThread() self.gps_thread.moveToThread(thread) self.gps_thread.gpsActivated.connect(self.gpsStarted) self.gps_thread.gpsPosition.connect(self.gpsPositionProvided) self.gps_thread.gpsDeactivated.connect(functools.partial(self.gpsStopped)) self.gps_thread.gpsError.connect(self.gpsErrorEncountered) #self.gps_thread.progress.connect(progressBar.setValue) thread.started.connect(self.gps_thread.startGPS) #thread.finished.connect(functools.partial(self.gpsStopped, thread)) thread.start() self.thread = thread TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems - attempting connection ", level=Qgis.Info) time.sleep(1.0) try: self.roamDistance = float(self.params.setParam("roamDistance")) except Exception as e: TOMsMessageLog.logMessage("In enableFeaturesWithGPSToolbarItems:init: roamDistance issue: {}".format(e), level=Qgis.Warning) self.roamDistance = 5.0 self.enableToolbarItems() self.createMapToolDict = {}
class NGWResourcesModelJob(QObject): started = pyqtSignal() statusChanged = pyqtSignal(str) warningOccurred = pyqtSignal(object) errorOccurred = pyqtSignal(object) finished = pyqtSignal() def __init__(self, parent, worker, model_response=None): """Create job. Arguments: job_id -- Job identification worker -- The class object inherits from NGWResourceModelJob """ QObject.__init__(self, parent) self.__result = None self.__worker = worker self.__job_id = self.__worker.id self.__error = None self.__warnings = [] # self.__job_id = "%s_%s" % (self.__worker.id, str(uuid.uuid1())) self.__worker.started.connect(self.started.emit) self.__worker.dataReceived.connect(self.__rememberResult) self.__worker.statusChanged.connect(self.statusChanged.emit) self.__worker.errorOccurred.connect(self.processJobError) self.__worker.warningOccurred.connect(self.processJobWarnings) self.model_response = model_response def setResponseObject(self, resp): self.model_response = resp self.model_response.job_id = self.__job_id def __rememberResult(self, result): self.__result = result def getJobId(self): return self.__job_id def getResult(self): return self.__result def error(self): return self.__error def processJobError(self, job_error): self.__error = job_error self.errorOccurred.emit(job_error) def processJobWarnings(self, job_error): if self.model_response: self.model_response._warnings.append(job_error) # self.warningOccurred.emit(job_error) def start(self): self.__thread = QThread(self) self.__worker.moveToThread(self.__thread) self.__worker.finished.connect(self.finishProcess) self.__thread.started.connect(self.__worker.run) self.__thread.start() def finishProcess(self): self.__worker.started.disconnect() self.__worker.dataReceived.disconnect() self.__worker.statusChanged.disconnect() self.__worker.errorOccurred.disconnect() self.__worker.warningOccurred.disconnect() self.__worker.finished.disconnect() self.__thread.quit() self.__thread.wait() self.finished.emit()
def accept(self): try: nodata = self.ui.IDC_tbNoDataExport.text() self.settings.nodata_value = int(nodata) if nodata != "" else None QgsMessageLog.logMessage("Maindlg: nodata: {0}".format(self.settings.nodata_value), "VoGis", Qgis.Info) if self.settings.onlyHektoMode is True and self.settings.mapData.rasters.count() > 0: self.settings.onlyHektoMode = False if self.settings.onlyHektoMode is False: if self.settings.mapData.rasters.count() < 1: retVal = QMessageBox.warning(self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Keine Rasterebene vorhanden oder sichtbar! Nur hektometrieren?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if retVal == QMessageBox.No: return else: self.settings.onlyHektoMode = True self.settings.createHekto = True if self.__getSettingsFromGui() is False: return if self.settings.onlyHektoMode is False: if len(self.settings.mapData.rasters.selectedRasters()) < 1: QMessageBox.warning(self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Kein Raster selektiert!")) return QgsMessageLog.logMessage("modeLine!=line: {0}".format(self.settings.modeLine != enumModeLine.line), "VoGis", Qgis.Info) QgsMessageLog.logMessage("customLine is None: {0}".format(self.settings.mapData.customLine is None), "VoGis", Qgis.Info) if self.settings.modeLine != enumModeLine.line and self.settings.mapData.customLine is None: QMessageBox.warning(self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Keine Profillinie vorhanden!")) return if len(self.settings.mapData.polygons.selected_polygons()) > 0 and len(self.settings.mapData.rasters.selectedRasters()) > 1: raster_names = list(raster.name for raster in self.settings.mapData.rasters.selectedRasters()) sel_raster, ok_clicked = QInputDialog.getItem( self.iface.mainWindow(), "DHM?", "Welches DHM soll zur Flächenverschneidung verwendet werden?", raster_names, 0, False ) if ok_clicked is False: return self.settings.intersection_dhm_idx = raster_names.index(sel_raster) QApplication.setOverrideCursor(Qt.WaitCursor) create_profile = CreateProfile(self.iface, self.settings) thread = QThread(self) create_profile.moveToThread(thread) create_profile.finished.connect(self.profiles_finished) create_profile.error.connect(self.profiles_error) create_profile.progress.connect(self.profiles_progress) thread.started.connect(create_profile.create) thread.start(QThread.LowestPriority) self.thread = thread self.create_profile = create_profile self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) except: QApplication.restoreOverrideCursor() ex = "{0}".format(traceback.format_exc()) msg = "Unexpected ERROR:\n\n{0}".format(ex[:2000]) QMessageBox.critical(self.iface.mainWindow(), "VoGIS-Profiltool", msg)
class LocatePointsDialog(QtWidgets.QDialog, FORM_CLASS): """Main dialog.""" def __init__(self, iface, parent=None): super(LocatePointsDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.in_combo.currentIndexChanged.connect(self.combo_changed) self.out_lyr.textChanged.connect(self.line_edit_text_changed) self.check_vertices.stateChanged.connect(self.checkbox_changed) self.run_button.clicked.connect(self.on_start) # Extra attributes self.in_name = False self.out_name = False # Extra thread attributes self.worker = None self.thread = None def on_start(self): self.pbar.setRange(0, 0) self.run_button.setEnabled(False) self.close_button.setEnabled(False) self.in_combo.setEnabled(False) self.out_lyr.setEnabled(False) self.offset.setEnabled(False) self.interval.setEnabled(False) self.check_attrs.setEnabled(False) self.check_vertices.setEnabled(False) self.check_endpoints.setEnabled(False) inlyr = self.in_combo.itemData(self.in_combo.currentIndex()) outlyr = self.out_lyr.text() offset = self.offset.value() interval = self.interval.value() keep_attrs = self.check_attrs.isChecked() add_ver = self.check_vertices.isChecked() add_end = self.check_endpoints.isChecked() self.worker = Worker(inlyr, outlyr, offset, interval, keep_attrs, add_ver, add_end) self.thread = QThread() self.worker.moveToThread(self.thread) self.worker.finished.connect(self.on_finished) self.thread.started.connect(self.worker.run) self.thread.start() def on_finished(self, error): vl = self.worker.vl self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() self.pbar.setRange(0, 10) self.pbar.setValue(10) self.run_button.setEnabled(True) self.close_button.setEnabled(True) self.in_combo.setEnabled(True) self.out_lyr.setEnabled(True) self.offset.setEnabled(True) self.interval.setEnabled(True) self.check_attrs.setEnabled(True) self.check_vertices.setEnabled(True) if self.check_vertices.isChecked() is False: self.check_endpoints.setEnabled(True) if error: self.iface.messageBar().pushMessage('Failed to create points', '{}'.format(error), level=1) else: try: QgsProject.instance().addMapLayer(vl) except AttributeError: QgsMapLayerRegistry.instance().addMapLayer(vl) self.iface.messageBar().pushMessage('Calculations finished', 'Points successfully created!', level=0) def combo_changed(self, idx): if idx > 0: crs = self.in_combo.itemData(self.in_combo.currentIndex()).crs() units = QgsUnitTypes.toString(crs.mapUnits()) self.offset.setToolTip('Offset value ({})'.format(units)) self.interval.setToolTip('Interval value ({})'.format(units)) self.in_name = True if self.out_name is True: self.run_button.setEnabled(True) else: self.run_button.setEnabled(False) else: self.offset.setToolTip('') self.interval.setToolTip('') self.in_name = False self.run_button.setEnabled(False) def line_edit_text_changed(self, text): if text: self.out_name = True if self.in_name is True: self.run_button.setEnabled(True) else: self.run_button.setEnabled(False) else: self.out_name = False self.run_button.setEnabled(False) def checkbox_changed(self, state): if state == 2: self.check_endpoints.setChecked(0) self.check_endpoints.setEnabled(False) else: self.check_endpoints.setEnabled(True)
class SearchPlugin(widget, base, Page): title = "Search" icon = resolve("search.svg") def __init__(self, api, parent=None): super(SearchPlugin, self).__init__(parent) self.setupUi(self) self.api = api self.project = None self.dbpath = None self.searchbox.textChanged.connect(self.search) self.searchbox.installEventFilter(self) self.clearButton.pressed.connect(self.searchbox.clear) self.resultsView.itemClicked.connect(self.jump_to) self.rebuildLabel.linkActivated.connect(self.rebuild_index) self.fuzzyCheck.stateChanged.connect(self.fuzzy_changed) self.indexbuilder = None self.indexthread = None roam.api.utils.install_touch_scroll(self.resultsView) def fuzzy_changed(self, state): self.search(self.searchbox.text()) def index_built(self, dbpath, timing): self.dbpath = dbpath self.resultsView.clear() self.searchbox.setEnabled(True) print("Index built in: {} seconds".format(timing)) def eventFilter(self, object, event): if event.type() == QEvent.FocusIn: RoamEvents.openkeyboard.emit() return False @property def db(self): db = sqlite3.connect(self.dbpath) db.create_function("rank", 1, make_rank_func((1., .1, 0, 0))) return db def project_loaded(self, project): self.project = project self.build_index(project) def rebuild_index(self): self.build_index(self.project) def build_index(self, project): self.searchbox.setEnabled(False) self.resultsView.setEnabled(False) self.resultsView.addItem("building search index...") validformat, settings = valid_search_settings(project.settings) if not validformat: RoamEvents.raisemessage("Searching", "Invalid search config.", level=1) self.searchbox.hide() self.resultsView.clear() self.resultsView.addItem("Invalid search config found") return self.indexthread = QThread() path = os.path.join(os.environ['APPDATA'], "roam", project.name) roam.utils.info("Search index path: {0}".format(path)) if not os.path.exists(path): os.makedirs(path) self.indexbuilder = IndexBuilder(path, settings) self.indexbuilder.moveToThread(self.indexthread) QgsProject.instance().removeAll.connect(self.indexthread.quit) self.indexbuilder.indexBuilt.connect(self.index_built) self.indexbuilder.finished.connect(self.indexthread.quit) self.indexthread.started.connect(self.indexbuilder.build_index) self.indexthread.finished.connect(self.indexbuilder.quit) self.indexthread.start() def search(self, text): db = self.db c = db.cursor() self.resultsView.clear() self.resultsView.setEnabled(False) if not text: return if self.fuzzyCheck.isChecked(): search = "* ".join(text.split()) + "*" else: search = text query = c.execute( """SELECT layer, featureid, snippet(search, '[',']') as snippet FROM search JOIN featureinfo on search.docid = featureinfo.id WHERE search match '{}' LIMIT 100""".format( search)).fetchall() for layer, featureid, snippet in query: item = QListWidgetItem() text = "{}\n {}".format(layer, snippet.replace('\n', ' ')) item.setText(text) item.setData(Qt.UserRole + 1, (layer, featureid, snippet)) self.resultsView.addItem(item) self.resultsView.setEnabled(True) if self.resultsView.count() == 0: self.resultsView.addItem("No Results") self.resultsView.setEnabled(False) db.close() def jump_to(self, item): data = item.data(Qt.UserRole + 1) if not data: return layername, fid = data[0], data[1] layer = roam.api.utils.layer_by_name(layername) feature = next(layer.getFeatures(QgsFeatureRequest(fid))) self.api.mainwindow.showmap() self.api.mainwindow.canvas.zoomToFeatureIds(layer, [fid]) RoamEvents.selectionchanged.emit({layer: [feature]})
def startWorker(self): """Initialises and starts the worker thread.""" try: layerindex = self.inputRaster.currentIndex() layerId = self.inputRaster.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) #inputlayer = QgsMapLayerRegistry.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return # create a reference to the layer that is being processed # (for use when creating the resulting raster layer) self.thinninglayer = inputlayer self.levels = [] #self.levelsListView.selectAll() #selected = self.levelsListView.selectedIndexes() if self.levelsListView.model().rowCount() == 0: self.showInfo("Levels must be specified!") return for i in range(self.levelsListView.model().rowCount()): levelstring = self.levelsListView.model().item(i).text() #for i in selected: # levelstring = self.levelsListView.model().itemData(i)[0] if self.intband: self.levels.append(int(levelstring)) else: self.levels.append(float(levelstring)) #self.levelsListView.clearSelection() # create a new worker instance worker = Worker(inputlayer, self.levels, self.intband) # configure the QgsMessageBar msgBar = self.iface.messageBar().createMessage( self.tr('Skeletonising'), '') self.aprogressBar = QProgressBar() self.aprogressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) acancelButton = QPushButton() acancelButton.setText(self.CANCEL) acancelButton.clicked.connect(self.killWorker) msgBar.layout().addWidget(self.aprogressBar) msgBar.layout().addWidget(acancelButton) # Has to be popped after the thread has finished (in # workerFinished). self.iface.messageBar().pushWidget(msgBar, Qgis.Info) self.messageBar = msgBar # start the worker in a new thread thread = QThread(self) worker.moveToThread(thread) worker.finished.connect(self.workerFinished) worker.error.connect(self.workerError) worker.status.connect(self.workerInfo) worker.progress.connect(self.progressBar.setValue) worker.progress.connect(self.aprogressBar.setValue) worker.iterprogress.connect(self.iterProgressBar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) except: import traceback self.showError(traceback.format_exc()) else: pass
def accept(self): try: nodata = self.ui.IDC_tbNoDataExport.text() self.settings.nodata_value = int(nodata) if nodata != "" else None QgsMessageLog.logMessage( "Maindlg: nodata: {0}".format(self.settings.nodata_value), "VoGis", Qgis.Info) if self.settings.onlyHektoMode is True and self.settings.mapData.rasters.count( ) > 0: self.settings.onlyHektoMode = False if self.settings.onlyHektoMode is False: if self.settings.mapData.rasters.count() < 1: retVal = QMessageBox.warning( self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate( "code", "Keine Rasterebene vorhanden oder sichtbar! Nur hektometrieren?" ), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if retVal == QMessageBox.No: return else: self.settings.onlyHektoMode = True self.settings.createHekto = True if self.__getSettingsFromGui() is False: return if self.settings.onlyHektoMode is False: if len(self.settings.mapData.rasters.selectedRasters()) < 1: QMessageBox.warning( self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Kein Raster selektiert!")) return QgsMessageLog.logMessage( "modeLine!=line: {0}".format( self.settings.modeLine != enumModeLine.line), "VoGis", Qgis.Info) QgsMessageLog.logMessage( "customLine is None: {0}".format( self.settings.mapData.customLine is None), "VoGis", Qgis.Info) if self.settings.modeLine != enumModeLine.line and self.settings.mapData.customLine is None: QMessageBox.warning( self.iface.mainWindow(), "VoGIS-Profiltool", QApplication.translate("code", "Keine Profillinie vorhanden!")) return if len(self.settings.mapData.polygons.selected_polygons() ) > 0 and len( self.settings.mapData.rasters.selectedRasters()) > 1: raster_names = list( raster.name for raster in self.settings.mapData.rasters.selectedRasters()) sel_raster, ok_clicked = QInputDialog.getItem( self.iface.mainWindow(), "DHM?", "Welches DHM soll zur Flächenverschneidung verwendet werden?", raster_names, 0, False) if ok_clicked is False: return self.settings.intersection_dhm_idx = raster_names.index( sel_raster) QApplication.setOverrideCursor(Qt.WaitCursor) create_profile = CreateProfile(self.iface, self.settings) thread = QThread(self) create_profile.moveToThread(thread) create_profile.finished.connect(self.profiles_finished) create_profile.error.connect(self.profiles_error) create_profile.progress.connect(self.profiles_progress) thread.started.connect(create_profile.create) thread.start(QThread.LowestPriority) self.thread = thread self.create_profile = create_profile self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) except: QApplication.restoreOverrideCursor() ex = "{0}".format(traceback.format_exc()) msg = "Unexpected ERROR:\n\n{0}".format(ex[:2000]) QMessageBox.critical(self.iface.mainWindow(), "VoGIS-Profiltool", msg)
class ResourceSharingDialog(QDialog, FORM_CLASS): TAB_ALL = 0 TAB_INSTALLED = 1 TAB_SETTINGS = 2 def __init__(self, parent=None, iface=None): """Constructor. :param parent: Optional widget to use as parent :type parent: QWidget :param iface: An instance of QGisInterface :type iface: QGisInterface """ super(ResourceSharingDialog, self).__init__(parent) self.setupUi(self) self.iface = iface # Reconfigure UI self.setModal(True) self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) self.button_install.setEnabled(False) self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Set QListWidgetItem # All icon_all = QIcon() icon_all.addFile( resources_path('img', 'plugin.svg'), QSize(), QIcon.Normal, QIcon.Off) item_all = QListWidgetItem() item_all.setIcon(icon_all) item_all.setText(self.tr('All')) # Installed icon_installed = QIcon() icon_installed.addFile( resources_path('img', 'plugin-installed.svg'), QSize(), QIcon.Normal, QIcon.Off) item_installed = QListWidgetItem() item_installed.setIcon(icon_installed) item_installed.setText(self.tr('Installed')) item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Settings icon_settings = QIcon() icon_settings.addFile( resources_path('img', 'settings.svg'), QSize(), QIcon.Normal, QIcon.Off) item_settings = QListWidgetItem() item_settings.setIcon(icon_settings) item_settings.setText(self.tr('Settings')) item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Add the list widget item to the widget self.menu_list_widget.addItem(item_all) self.menu_list_widget.addItem(item_installed) self.menu_list_widget.addItem(item_settings) # Init the message bar self.message_bar = QgsMessageBar(self) self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.vlayoutRightColumn.insertWidget(0, self.message_bar) # Progress dialog for any long running process self.progress_dialog = None # Init repository manager self.repository_manager = RepositoryManager() self.collection_manager = CollectionManager() # Collections list view self.collections_model = QStandardItemModel(0, 1) self.collections_model.sort(0, Qt.AscendingOrder) self.collection_proxy = CustomSortFilterProxyModel(self) self.collection_proxy.setSourceModel(self.collections_model) self.list_view_collections.setModel(self.collection_proxy) # Active selected collection self._selected_collection_id = None # Slots self.button_add.clicked.connect(self.add_repository) self.button_edit.clicked.connect(self.edit_repository) self.button_delete.clicked.connect(self.delete_repository) self.button_reload.clicked.connect(self.reload_repositories) self.menu_list_widget.currentRowChanged.connect(self.set_current_tab) self.list_view_collections.selectionModel().currentChanged.connect( self.on_list_view_collections_clicked) self.line_edit_filter.textChanged.connect(self.filter_collections) self.button_install.clicked.connect(self.install_collection) self.button_open.clicked.connect(self.open_collection) self.button_uninstall.clicked.connect(self.uninstall_collection) self.button_box.button(QDialogButtonBox.Help).clicked.connect( self.open_help) # Populate repositories widget and collections list view self.populate_repositories_widget() self.reload_collections_model() def set_current_tab(self, index): """Set stacked widget based on active tab. :param index: The index of the active list widget item. :type index: int """ # Clear message bar first self.message_bar.clearWidgets() if index == (self.menu_list_widget.count() - 1): # Switch to settings tab self.stacked_menu_widget.setCurrentIndex(1) else: # Switch to plugins tab if index == 1: # Installed self.collection_proxy.accepted_status = \ COLLECTION_INSTALLED_STATUS # Set the web view title = self.tr('Installed Collections') description = self.tr( 'On the left you see the list of all collections ' 'installed on your QGIS') else: # All self.collection_proxy.accepted_status = COLLECTION_ALL_STATUS # Set the web view title = self.tr('All Collections') description = self.tr( 'On the left you see the list of all collections ' 'available from the repositories registered in the ' 'settings.') context = { 'resources_path': resources_path(), 'title': title, 'description': description } self.web_view_details.setHtml( render_template('tab_description.html', context)) self.stacked_menu_widget.setCurrentIndex(0) def add_repository(self): """Open add repository dialog.""" dlg = ManageRepositoryDialog(self) if not dlg.exec_(): return for repo in self.repository_manager.directories.values(): if dlg.line_edit_url.text().strip() == repo['url']: self.message_bar.pushMessage( self.tr( 'Unable to add another repository with the same URL!'), Qgis.Critical, 5) return repo_name = dlg.line_edit_name.text() repo_url = dlg.line_edit_url.text().strip() repo_auth_cfg = dlg.line_edit_auth_id.text().strip() if repo_name in self.repository_manager.directories: repo_name += '(2)' # Show progress dialog self.show_progress_dialog("Fetching repository's metadata") # Add repository try: status, description = self.repository_manager.add_directory( repo_name, repo_url, repo_auth_cfg) if status: self.message_bar.pushMessage( self.tr( 'Repository is successfully added'), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr( 'Unable to add repository: %s') % description, Qgis.Critical, 5) except Exception as e: self.message_bar.pushMessage( self.tr('%s') % e, Qgis.Critical, 5) finally: self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() # Deactivate edit and delete button self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def edit_repository(self): """Open edit repository dialog.""" selected_item = self.tree_repositories.currentItem() if selected_item: repo_name = selected_item.text(0) if not repo_name: return # Check if it's the approved online dir repository settings = QSettings() settings.beginGroup(repo_settings_group()) if settings.value(repo_name + '/url') in \ self.repository_manager._online_directories.values(): self.message_bar.pushMessage( self.tr( 'You can not edit the official repositories!'), Qgis.Warning, 5) return dlg = ManageRepositoryDialog(self) dlg.line_edit_name.setText(repo_name) dlg.line_edit_url.setText( self.repository_manager.directories[repo_name]['url']) dlg.line_edit_auth_id.setText( self.repository_manager.directories[repo_name]['auth_cfg']) if not dlg.exec_(): return # Check if the changed URL is already there in the repo new_url = dlg.line_edit_url.text().strip() old_url = self.repository_manager.directories[repo_name]['url'] for repo in self.repository_manager.directories.values(): if new_url == repo['url'] and (old_url != new_url): self.message_bar.pushMessage( self.tr('Unable to add another repository with the same ' 'URL!'), Qgis.Critical, 5) return new_name = dlg.line_edit_name.text() if (new_name in self.repository_manager.directories) and ( new_name != repo_name): new_name += '(2)' new_auth_cfg = dlg.line_edit_auth_id.text() # Show progress dialog self.show_progress_dialog("Fetching repository's metadata") # Edit repository try: status, description = self.repository_manager.edit_directory( repo_name, new_name, old_url, new_url, new_auth_cfg ) if status: self.message_bar.pushMessage( self.tr('Repository is successfully updated'), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr('Unable to add repository: %s') % description, Qgis.Critical, 5) except Exception as e: self.message_bar.pushMessage( self.tr('%s') % e, Qgis.Critical, 5) finally: self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() # Deactivate edit and delete button self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def delete_repository(self): """Delete a repository in the tree widget.""" selected_item = self.tree_repositories.currentItem() if selected_item: repo_name = selected_item.text(0) if not repo_name: return # Check if it's the approved online dir repository repo_url = self.repository_manager.directories[repo_name]['url'] if repo_url in self.repository_manager._online_directories.values(): self.message_bar.pushMessage( self.tr( 'You can not remove the official repositories!'), Qgis.Warning, 5) return warning = self.tr('Are you sure you want to remove the following ' 'repository?') + '\n' + repo_name if QMessageBox.warning( self, self.tr('QGIS Resource Sharing'), warning, QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: return # Remove repository installed_collections = \ self.collection_manager.get_installed_collections(repo_url) if installed_collections: message = ('You have some installed collections from this ' 'repository. Please uninstall them first!') self.message_bar.pushMessage(message, Qgis.Warning, 5) else: self.repository_manager.remove_directory(repo_name) # Reload data and widget self.reload_data_and_widget() # Deactivate edit and delete button self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def reload_repositories(self): """Slot for when user clicks reload repositories button.""" # Show progress dialog self.show_progress_dialog('Reloading all repositories') for repo_name in self.repository_manager.directories: directory = self.repository_manager.directories[repo_name] url = directory['url'] auth_cfg = directory['auth_cfg'] try: status, description = self.repository_manager.reload_directory( repo_name, url, auth_cfg) if status: self.message_bar.pushMessage( self.tr( 'Repository %s is successfully reloaded') % repo_name, Qgis.Info, 5) else: self.message_bar.pushMessage( self.tr( 'Unable to reload %s: %s') % ( repo_name, description), Qgis.Critical, 5) except Exception as e: self.message_bar.pushMessage( self.tr('%s') % e, Qgis.Critical, 5) self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() def install_collection(self): """Slot for when user clicks download button.""" self.show_progress_dialog('Starting installation process...') self.progress_dialog.canceled.connect(self.install_canceled) self.installer_thread = QThread() self.installer_worker = CollectionInstaller( self.collection_manager, self._selected_collection_id) self.installer_worker.moveToThread(self.installer_thread) self.installer_worker.finished.connect(self.install_finished) self.installer_worker.aborted.connect(self.install_aborted) self.installer_worker.progress.connect(self.install_progress) self.installer_thread.started.connect(self.installer_worker.run) self.installer_thread.start() def install_finished(self): # Process the result self.progress_dialog.hide() if self.installer_worker.install_status: self.reload_collections_model() message = '%s is installed successfully' % ( config.COLLECTIONS[self._selected_collection_id]['name']) else: message = self.installer_worker.error_message QMessageBox.information(self, 'Resource Sharing', message) # Clean up the worker and thread self.installer_worker.deleteLater() self.installer_thread.quit() self.installer_thread.wait() self.installer_thread.deleteLater() def install_canceled(self): self.progress_dialog.hide() self.show_progress_dialog('Cancelling installation...') self.installer_worker.abort() def install_aborted(self): if self.installer_thread.isRunning(): self.installer_thread.quit() self.installer_thread.finished.connect(self.progress_dialog.hide) def install_progress(self, text): self.progress_dialog.setLabelText(text) def uninstall_collection(self): """Slot called when user clicks uninstall button.""" try: self.collection_manager.uninstall(self._selected_collection_id) except Exception as e: raise self.reload_collections_model() QMessageBox.information( self, 'Resource Sharing', 'The collection is uninstalled succesfully!') def open_collection(self): """Slot for when user clicks 'Open' button.""" collection_path = local_collection_path(self._selected_collection_id) directory_url = QUrl.fromLocalFile(collection_path) QDesktopServices.openUrl(directory_url) def reload_data_and_widget(self): """Reload repositories and collections and update widgets related.""" self.reload_repositories_widget() self.reload_collections_model() def reload_repositories_widget(self): """Refresh tree repositories using new repositories data.""" self.repository_manager.load_directories() self.populate_repositories_widget() def populate_repositories_widget(self): """Populate the current dictionary repositories to the tree widget.""" # Clear the current tree widget self.tree_repositories.clear() # Export the updated ones from the repository manager for repo_name in self.repository_manager.directories: url = self.repository_manager.directories[repo_name]['url'] item = QTreeWidgetItem(self.tree_repositories) item.setText(0, repo_name) item.setText(1, url) self.tree_repositories.resizeColumnToContents(0) self.tree_repositories.resizeColumnToContents(1) self.tree_repositories.sortItems(1, Qt.AscendingOrder) def reload_collections_model(self): """Reload the collections model with the current collections.""" self.collections_model.clear() for id in config.COLLECTIONS: collection_name = config.COLLECTIONS[id]['name'] collection_author = config.COLLECTIONS[id]['author'] collection_tags = config.COLLECTIONS[id]['tags'] collection_description = config.COLLECTIONS[id]['description'] collection_status = config.COLLECTIONS[id]['status'] item = QStandardItem(collection_name) item.setEditable(False) item.setData(id, COLLECTION_ID_ROLE) item.setData(collection_name, COLLECTION_NAME_ROLE) item.setData(collection_description, COLLECTION_DESCRIPTION_ROLE) item.setData(collection_author, COLLECTION_AUTHOR_ROLE) item.setData(collection_tags, COLLECTION_TAGS_ROLE) item.setData(collection_status, COLLECTION_STATUS_ROLE) self.collections_model.appendRow(item) self.collections_model.sort(0, Qt.AscendingOrder) def on_tree_repositories_itemSelectionChanged(self): """Slot for when the itemSelectionChanged signal emitted.""" # Activate edit and delete button self.button_edit.setEnabled(True) self.button_delete.setEnabled(True) def on_list_view_collections_clicked(self, index): """Slot for when the list_view_collections is clicked.""" real_index = self.collection_proxy.mapToSource(index) if real_index.row() != -1: collection_item = self.collections_model.itemFromIndex(real_index) collection_id = collection_item.data(COLLECTION_ID_ROLE) self._selected_collection_id = collection_id # Enable/disable button status = config.COLLECTIONS[self._selected_collection_id]['status'] is_installed = status == COLLECTION_INSTALLED_STATUS if is_installed: self.button_install.setEnabled(True) self.button_install.setText('Reinstall') self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) else: self.button_install.setEnabled(True) self.button_install.setText('Install') self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Show metadata self.show_collection_metadata(collection_id) @pyqtSlot(str) def filter_collections(self, text): search = QRegExp( text, Qt.CaseInsensitive, QRegExp.RegExp) self.collection_proxy.setFilterRegExp(search) def show_collection_metadata(self, id): """Show the collection metadata given the id.""" html = self.collection_manager.get_html(id) self.web_view_details.setHtml(html) def reject(self): """Slot when the dialog is closed.""" # Serialize collections to settings self.repository_manager.serialize_repositories() self.done(0) def open_help(self): """Open help.""" doc_url = QUrl('http://www.akbargumbira.com/qgis_resources_sharing') QDesktopServices.openUrl(doc_url) def show_progress_dialog(self, text): """Show infinite progress dialog with given text. :param text: Text as the label of the progress dialog :type text: str """ if self.progress_dialog is None: self.progress_dialog = QProgressDialog(self) self.progress_dialog.setWindowModality(Qt.WindowModal) self.progress_dialog.setAutoClose(False) title = self.tr('Resource Sharing') self.progress_dialog.setWindowTitle(title) # Just use infinite progress bar here self.progress_dialog.setMaximum(0) self.progress_dialog.setMinimum(0) self.progress_dialog.setValue(0) self.progress_dialog.setLabelText(text) self.progress_dialog.show()
class GisFIRELightnings: """ GisFIRE Lightnings QGIS plugin implementation TODO: Add attributes information """ iface: QgisInterface def __init__(self, iface: QgisInterface): """ 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: qgis.gui.QgisInterface """ # Save reference to the QGIS interface 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', 'gisfire_lightnings_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) # Initialization of UI references self._toolbar_actions = dict() self._menu_actions = dict() self._menu = None self._menu_gisfire = None self._toolbar = None self._dlg = None # Initialization of GisFIRE data layers self._layers = {} # noinspection PyMethodMayBeStatic def tr(self, message: str) -> str: """ Get the translation for a string using Qt translation API. :param message: String for translation. :type message: str :returns: Translated version of message. :rtype: str """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('GisFIRELightnings', message) def __add_toolbar_actions(self): """ Creates the toolbar buttons that GisFIRE Lightnings uses as shortcuts. """ # Setup parameters action = QAction(QIcon(':/gisfire_lightnings/setup.png'), self.tr('Setup GisFIRE Lightnings'), None) action.triggered.connect( self.__on_setup ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 action.setEnabled(True) action.setCheckable(False) action.setStatusTip(self.tr('Setup GisFIRE Lightnings')) action.setWhatsThis(self.tr('Setup GisFIRE Lightnings')) self._toolbar.addAction(action) self._toolbar_actions['setup'] = action # Separator self._toolbar.addSeparator() # Download lightnings action = QAction(QIcon(':/gisfire_lightnings/download-lightnings.png'), self.tr('Download Lightnings'), None) action.triggered.connect( self.__on_download_lightnings ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 action.setEnabled(True) action.setCheckable(False) action.setStatusTip(self.tr('Download Lightnings')) action.setWhatsThis(self.tr('Download Lightnings')) self._toolbar.addAction(action) self._toolbar_actions['download-lightnings'] = action """ # Clip lightnings action = QAction( QIcon(':/gisfire_lightnings/clip-lightnings.png'), self.tr('Clip lightnings on layer and features'), None ) action.triggered.connect(self.onClipLightnings) action.setEnabled(True) action.setCheckable(False) action.setStatusTip(self.tr('Clip lightnings on layer and features')) action.setWhatsThis(self.tr('Clip lightnings on layer and features')) self._toolbar.addAction(action) self._toolbar_actions['clip-lightnings'] = action # Clip lightnings action = QAction( QIcon(':/gisfire_lightnings/filter-lightnings.png'), self.tr('Filter lightnings'), None ) action.triggered.connect(self.onFilterLightnings) action.setEnabled(True) action.setCheckable(False) action.setStatusTip(self.tr('Filter lightnings')) action.setWhatsThis(self.tr('Filter lightnings')) self._toolbar.addAction(action) self._toolbar_actions['filter-lightnings'] = action # Process lightnings action = QAction( QIcon(':/gisfire_lightnings/process-lightnings.png'), self.tr('Calculate lightnings route'), None ) action.triggered.connect(self.onProcessLightnings) action.setEnabled(True) action.setCheckable(False) action.setStatusTip(self.tr('Calculate lightnings route')) action.setWhatsThis(self.tr('Calculate lightnings route')) self._toolbar.addAction(action) self._toolbar_actions['process-lightnings'] = action""" def __add_menu_actions(self): """ Creates the menu entries that allow GisFIRE procedures. """ # Setup parameters action: QAction = self._menu.addAction(self.tr('Setup')) action.setIcon(QIcon(':/gisfire_lightnings/setup.png')) action.setIconVisibleInMenu(True) action.triggered.connect( self.__on_setup ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._menu_actions['setup'] = action # Download lightnings action = self._menu.addAction(self.tr('Download Lightnings')) action.setIcon(QIcon(':/gisfire_lightnings/download-lightnings.png')) action.setIconVisibleInMenu(True) action.triggered.connect( self.__on_download_lightnings ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._menu_actions['download-lightnings'] = action """# Clip lightnings action = self._menu.addAction(self.tr('Clip lightnings on layer and features')) action.setIcon(QIcon(':/gisfire_lightnings/clip-lightnings.png')) action.setIconVisibleInMenu(True) action.triggered.connect(self.onClipLightnings) self._menu_actions['clip-lightnings'] = action # Filter lightnings action = self._menu.addAction(self.tr('Filter lightnings')) action.setIcon(QIcon(':/gisfire_lightnings/filter-lightnings.png')) action.setIconVisibleInMenu(True) action.triggered.connect(self.onFilterLightnings) self._menu_actions['clip-lightnings'] = action # Process lightnings action = self._menu.addAction(self.tr('Calculate lightnings route')) action.setIcon(QIcon(':/gisfire_lightnings/process-lightnings.png')) action.setIconVisibleInMenu(True) action.triggered.connect(self.onProcessLightnings) self._menu_actions['process-lightnings'] = action""" def __add_relations(self): """ Creates mutually exclusive relations between toolbar buttons. """ pass # noinspection PyPep8Naming # noinspection DuplicatedCode def initGui(self): """ Initializes the QGIS GUI for the GisFIRE Lightning plugin. """ # Set up the menu menu_name = self.tr(u'Gis&FIRE') parent_menu = self.iface.mainWindow().menuBar() # Search if the menu exists (there are other GisFIRE modules installed) for action in parent_menu.actions(): if action.text() == menu_name: self._menu_gisfire = action.menu() # Create the menu if it does not exist and add it to the current menubar if self._menu_gisfire is None: self._menu_gisfire = QMenu(menu_name, parent_menu) actions = parent_menu.actions() if len(actions) > 0: self.iface.mainWindow().menuBar().insertMenu( actions[-1], self._menu_gisfire) else: self.iface.mainWindow().menuBar().addMenu(self._menu_gisfire) # Create Lightnings menu self._menu = QMenu(self.tr(u'Lightnings'), self._menu_gisfire) self._menu.setIcon(QIcon(':/gisfire_lightnings/lightnings.png')) self._menu_gisfire.addMenu(self._menu) # Set up the toolbar for lightnings plugin self._toolbar = self.iface.addToolBar(u'GisFIRE Lightnings') self._toolbar.setObjectName(u'GisFIRE Lightnings') # Add toolbar buttons self.__add_toolbar_actions() # Add menu entries self.__add_menu_actions() # Create relations with existing menus and buttons self.__add_relations() # noinspection DuplicatedCode def unload(self): """ Removes the plugin menu item and icon from QGIS GUI. """ # Remove toolbar items for action in self._toolbar_actions.values(): action.triggered.disconnect() self.iface.removeToolBarIcon(action) action.deleteLater() # Remove toolbar if not (self._toolbar is None): self._toolbar.deleteLater() # Remove menu items for action in self._menu_actions.values(): action.triggered.disconnect() self._menu.removeAction(action) action.deleteLater() # Remove menu if not (self._menu is None): self._menu.deleteLater() # Remove the menu_gisfire only if I'm the only GisFIRE module installed count = 0 for name in qgis.utils.active_plugins: if name.startswith('gisfire'): count += 1 if count == 1: if not (self._menu_gisfire is None): self.iface.mainWindow().menuBar().removeAction( self._menu_gisfire.menuAction()) self._menu_gisfire.menuAction().deleteLater() self._menu_gisfire.deleteLater() def __on_setup(self): # TODO: Improve setting enabling and disabling data providers self._dlg = DlgSettings(self.iface.mainWindow()) qgs_settings = QgsSettings() # Get values and initialize dialog self._dlg.meteocat_api_key = qgs_settings.value( "gisfire_lightnings/meteocat_api_key", "") self._dlg.gisfire_api_url = qgs_settings.value( "gisfire_lightnings/gisfire_api_url", "") self._dlg.gisfire_api_username = qgs_settings.value( "gisfire_lightnings/gisfire_api_username", "") self._dlg.gisfire_api_token = qgs_settings.value( "gisfire_lightnings/gisfire_api_token", "") result = self._dlg.exec_() if result == QDialog.Accepted: # Store correct values qgs_settings.setValue("gisfire_lightnings/meteocat_api_key", self._dlg.meteocat_api_key) qgs_settings.setValue("gisfire_lightnings/gisfire_api_url", self._dlg.gisfire_api_url) qgs_settings.setValue("gisfire_lightnings/gisfire_api_username", self._dlg.gisfire_api_username) qgs_settings.setValue("gisfire_lightnings/gisfire_api_token", self._dlg.gisfire_api_token) def __on_download_lightnings(self): # Get values and initialize dialog qgs_settings: QgsSettings = QgsSettings() meteocat_api_key = qgs_settings.value( "gisfire_lightnings/meteocat_api_key", "") gisfire_api_url: str = qgs_settings.value( "gisfire_lightnings/gisfire_api_url", "") gisfire_api_username: str = qgs_settings.value( "gisfire_lightnings/gisfire_api_username", "") gisfire_api_token: str = qgs_settings.value( "gisfire_lightnings/gisfire_api_token", "") # Errors if meteocat_api_key == "" or len(meteocat_api_key) < 10: self.iface.messageBar().pushMessage( "", self.tr("MeteoCat API Key missing"), level=Qgis.Critical, duration=5) msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(self.tr("Error")) msg.setInformativeText(self.tr("MeteoCat API Key missing")) msg.setWindowTitle(self.tr("Error")) msg.exec_() return if gisfire_api_url == "" or len(gisfire_api_url) < 10: self.iface.messageBar().pushMessage( "", self.tr("GisFIRE API URL missing"), level=Qgis.Critical, duration=5) msg: QMessageBox = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(self.tr("Error")) msg.setInformativeText(self.tr("GisFIRE API URL missing")) msg.setWindowTitle(self.tr("Error")) msg.exec_() return if gisfire_api_username == "" or len(gisfire_api_username) < 1: self.iface.messageBar().pushMessage( "", self.tr("GisFIRE API Username missing"), level=Qgis.Critical, duration=5) msg: QMessageBox = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(self.tr("Error")) msg.setInformativeText(self.tr("GisFIRE API Username missing")) msg.setWindowTitle(self.tr("Error")) msg.exec_() return if gisfire_api_token == "" or len(gisfire_api_token) < 10: self.iface.messageBar().pushMessage( "", self.tr("GisFIRE API token missing"), level=Qgis.Critical, duration=5) msg: QMessageBox = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(self.tr("Error")) msg.setInformativeText(self.tr("GisFIRE API token missing")) msg.setWindowTitle(self.tr("Error")) msg.exec_() return # Check that the layers do not exist already and can cause conflicts layer_names: List[str] = [ layer.name() for layer in QgsProject.instance().mapLayers().values() ] if self.tr('lightnings') in layer_names or self.tr( 'lightnings-measurement-error') in layer_names: self.iface.messageBar().pushMessage( "", self. tr("Lightning layers exists, please remove them before downloading " "new lightnings"), level=Qgis.Critical, duration=5) return # Show dialog dlg: DlgDownloadLightnings = DlgDownloadLightnings( self.iface.mainWindow(), ['MeteoCat']) result = dlg.exec_() if result == QDialog.Accepted: day: datetime.date = dlg.download_day self._thread_download_meteocat = QThread() self._worker_download_meteocat = DownloadLightningsUsingMeteocat( day, gisfire_api_url, gisfire_api_username, gisfire_api_token, meteocat_api_key, 25831) self._worker_download_meteocat.moveToThread( self._thread_download_meteocat) self._thread_download_meteocat.started.connect( self._worker_download_meteocat.run ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._worker_download_meteocat.finished.connect( self._thread_download_meteocat.quit ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._worker_download_meteocat.finished.connect( self._worker_download_meteocat.deleteLater ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._thread_download_meteocat.finished.connect( self._thread_download_meteocat.deleteLater ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._worker_download_meteocat.progress.connect( self.__report_progress_download_lightnings_meteocat ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._worker_download_meteocat.data.connect( self.__received_data_download_lightnings_meteocat ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self._worker_download_meteocat.finished.connect( self.__report_end_download_lightnings_meteocat ) # noqa known issue https://youtrack.jetbrains.com/issue/PY-22908 self.iface.messageBar().pushMessage( "", self.tr( "Downloading Meteo.cat Lightning data through GisFIRE API." ), level=Qgis.Info, duration=1) self._thread_download_meteocat.start() def __report_progress_download_lightnings_meteocat(self, n): pass def __report_end_download_lightnings_meteocat(self): self.iface.messageBar().pushMessage("", self.tr("Finished."), level=Qgis.Info, duration=1) def __received_data_download_lightnings_meteocat( self, lightnings: List[Lightning]): self.iface.messageBar().pushMessage( "", self.tr("Received {0:d} lightnings.".format(len(lightnings))), level=Qgis.Info, duration=1) self.lightnings_layer = create_lightnings_layer( 'Point', 'lightnings', 25831) self.lightning_errors_layer = create_lightnings_layer( 'Polygon', 'lightning-errors', 25831) add_layer_in_position(self.lightnings_layer, 1) add_layer_in_position(self.lightning_errors_layer, 2) for lightning in lightnings: add_lightning_point(self.lightnings_layer, lightning) add_lightning_polygon(self.lightning_errors_layer, lightning)
class NNJoinDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" self.iface = iface self.plugin_dir = dirname(__file__) # Some translated text (to enable reuse) self.NNJOIN = self.tr('NNJoin') self.CANCEL = self.tr('Cancel') self.CLOSE = self.tr('Close') self.HELP = self.tr('Help') self.OK = self.tr('OK') super(NNJoinDialog, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html#\ # widgets-and-dialogs-with-auto-connect self.setupUi(self) # Modify ui components okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) self.cancelButton = self.button_box.button(QDialogButtonBox.Cancel) self.cancelButton.setText(self.CANCEL) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) self.approximate_input_geom_cb.setCheckState(Qt.Unchecked) self.approximate_input_geom_cb.setVisible(False) stytxt = "QCheckBox:checked {color: red; background-color: white}" self.approximate_input_geom_cb.setStyleSheet(stytxt) self.use_indexapprox_cb.setCheckState(Qt.Unchecked) self.use_indexapprox_cb.setVisible(False) self.use_indexapprox_cb.setStyleSheet(stytxt) self.use_index_nonpoint_cb.setCheckState(Qt.Unchecked) self.use_index_nonpoint_cb.setVisible(False) self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) # Help button helpButton = self.helpButton helpButton.setText(self.HELP) # Connect signals okButton.clicked.connect(self.startWorker) # self.cancelButton.clicked.connect(self.killWorker) closeButton.clicked.connect(self.reject) helpButton.clicked.connect(self.help) self.approximate_input_geom_cb.stateChanged['int'].connect( self.useindexchanged) self.use_indexapprox_cb.stateChanged['int'].connect( self.useindexchanged) self.use_index_nonpoint_cb.stateChanged['int'].connect( self.useindexchanged) inpIndexCh = self.inputVectorLayer.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) joinIndexCh = self.joinVectorLayer.currentIndexChanged['QString'] # joinIndexCh.connect(self.layerchanged) joinIndexCh.connect(self.joinlayerchanged) # self.distancefieldname.editingFinished.connect(self.fieldchanged) self.distancefieldname.textChanged.connect(self.distfieldchanged) self.joinPrefix.editingFinished.connect(self.fieldchanged) theRegistry = QgsProject.instance() theRegistry.layersAdded.connect(self.layerlistchanged) theRegistry.layersRemoved.connect(self.layerlistchanged) # Disconnect the cancel button to avoid exiting. self.button_box.rejected.disconnect(self.reject) # Set instance variables self.mem_layer = None self.worker = None self.inputlayerid = None self.joinlayerid = None self.layerlistchanging = False def startWorker(self): """Initialises and starts the worker thread.""" try: layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) if joinlayer is None: self.showError(self.tr('No join layer defined')) return if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') outputlayername = self.outputDataset.text() approximateinputgeom = self.approximate_input_geom_cb.isChecked() joinprefix = self.joinPrefix.text() # useindex = True useindex = self.use_index_nonpoint_cb.isChecked() useindexapproximation = self.use_indexapprox_cb.isChecked() distancefieldname = self.distancefieldname.text() selectedinputonly = self.inputSelected.isChecked() selectedjoinonly = self.joinSelected.isChecked() excludecontaining = self.exclude_containing_poly_cb.isChecked() # create a new worker instance self.worker = Worker(inputlayer, joinlayer, outputlayername, joinprefix, distancefieldname, approximateinputgeom, useindexapproximation, useindex, selectedinputonly, selectedjoinonly, excludecontaining) # configure the QgsMessageBar msgBar = self.iface.messageBar().createMessage( self.tr('Joining'), '') self.aprogressBar = QProgressBar() self.aprogressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) acancelButton = QPushButton() acancelButton.setText(self.CANCEL) # acancelButton.clicked.connect(self.killWorker) msgBar.layout().addWidget(self.aprogressBar) msgBar.layout().addWidget(acancelButton) # Has to be popped after the thread has finished (in # workerFinished). self.iface.messageBar().pushWidget(msgBar, Qgis.Info) # self.iface.messageBar().INFO) self.messageBar = msgBar # start the worker in a new thread self.mythread = QThread(self) # QT requires the "self" self.worker.status.connect(self.workerInfo) self.worker.progress.connect(self.progressBar.setValue) self.worker.progress.connect(self.aprogressBar.setValue) self.worker.finished.connect(self.workerFinished) self.worker.error.connect(self.workerError) # Must come before movetothread: self.cancelButton.clicked.connect(self.worker.kill) acancelButton.clicked.connect(self.worker.kill) self.worker.finished.connect(self.worker.deleteLater) self.worker.finished.connect(self.mythread.quit) # self.worker.error.connect(self.worker.deleteLater) # self.worker.error.connect(self.mythread.quit) # Must come before thread.started.connect!: self.worker.moveToThread(self.mythread) self.mythread.started.connect(self.worker.run) self.mythread.finished.connect(self.mythread.deleteLater) self.mythread.start() # self.thread = thread # self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) if layerId == joinlayerId: self.showInfo("The join layer is the same as the" " input layer - doing a self join!") except: import traceback self.showError("Error starting worker: " + traceback.format_exc()) else: pass # End of startworker def workerFinished(self, ok, ret): """Handles the output from the worker and cleans up after the worker has finished.""" # remove widget from message bar (pop) self.iface.messageBar().popWidget(self.messageBar) if ok and ret is not None: # report the result mem_layer = ret QgsMessageLog.logMessage(self.tr('NNJoin finished'), self.NNJOIN, Qgis.Info) mem_layer.dataProvider().updateExtents() mem_layer.commitChanges() self.layerlistchanging = True QgsProject.instance().addMapLayer(mem_layer) self.layerlistchanging = False else: # notify the user that something went wrong if not ok: self.showError(self.tr('Aborted') + '!') else: self.showError(self.tr('No layer created') + '!') self.progressBar.setValue(0.0) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.button_box.button(QDialogButtonBox.Close).setEnabled(True) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) # End of workerFinished def workerError(self, exception_string): """Report an error from the worker.""" self.showError(exception_string) def workerInfo(self, message_string): """Report an info message from the worker.""" QgsMessageLog.logMessage(self.tr('Worker') + ': ' + message_string, self.NNJOIN, Qgis.Info) def fieldchanged(self, number=0): # If the layer list is being updated, don't do anything if self.layerlistchanging: return self.updateui() # End of fieldchanged def distfieldchanged(self, number=0): # If the layer list is being updated, don't do anything # if self.layerlistchanging: # return # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Enable the OK button (if layers are OK) if inputlayer is not None and joinlayer is not None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) if inputlayer is not None: # Set the default background (white) for the distance field name self.distancefieldname.setStyleSheet("background:#fff;") # Check if the distance field name already is used inputfields = inputlayer.fields().toList() for infield in inputfields: if infield.name() == self.distancefieldname.text(): self.distancefieldname.setStyleSheet("background:#f00;") self.showInfo( "Distance field name conflict in input layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) if joinlayer is not None: joinfields = joinlayer.fields().toList() for joinfield in joinfields: if (self.joinPrefix.text() + joinfield.name() == self.distancefieldname.text()): self.distancefieldname.setStyleSheet( "background:#f00;") self.showInfo( "Distance field name conflict in join layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) # self.updateui() # End of distfieldchanged def joinlayerchanged(self, number=0): # If the layer list is being updated, don't do anything if self.layerlistchanging: return # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) self.joinlayerid = joinlayerId joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Geographic? - give a warning! if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') self.layerchanged() # End of joinlayerchanged def layerchanged(self, number=0): """Do the necessary updates after a layer selection has been changed.""" # If the layer list is being updated, don't do anything if self.layerlistchanging: return # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) self.inputlayerid = layerId inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) self.joinlayerid = joinlayerId joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Update the input layer UI label with input geometry # type information if inputlayer is not None: inputwkbtype = inputlayer.wkbType() inputlayerwkbtext = self.getwkbtext(inputwkbtype) self.inputgeometrytypelabel.setText(inputlayerwkbtext) # Update the join layer UI label with join geometry type # information if joinlayer is not None: joinwkbtype = joinlayer.wkbType() joinlayerwkbtext = self.getwkbtext(joinwkbtype) self.joingeometrytypelabel.setText(joinlayerwkbtext) # Check the coordinate systems # Different CRSs? - give a warning! if (inputlayer is not None and joinlayer is not None and inputlayer.crs() != joinlayer.crs()): self.showWarning( 'Layers have different CRS! - Input CRS authid: ' + str(inputlayer.crs().authid()) + ' - Join CRS authid: ' + str(joinlayer.crs().authid()) + ". The input layer will be transformed.") self.updateui() # end of layerchanged def useindexchanged(self, number=0): self.updateui() def layerlistchanged(self): # When a layer has been added to or removed by the user, # the comboboxes should be updated to include the new # possibilities. self.layerlistchanging = True # Repopulate the input and join layer combo boxes # Save the currently selected input layer inputlayerid = self.inputlayerid layers = QgsProject.instance().mapLayers() layerslist = [] for id in layers.keys(): if layers[id].type() == QgsMapLayer.VectorLayer: if not layers[id].isValid(): QMessageBox.information(None, self.tr('Information'), 'Layer ' + layers[id].name() + ' is not valid') if layers[id].wkbType() != QgsWkbTypes.NoGeometry: layerslist.append((layers[id].name(), id)) # Add the layers to the input layers combobox self.inputVectorLayer.clear() for layerdescription in layerslist: self.inputVectorLayer.addItem(layerdescription[0], layerdescription[1]) # Set the previous selection for the input layer for i in range(self.inputVectorLayer.count()): if self.inputVectorLayer.itemData(i) == inputlayerid: self.inputVectorLayer.setCurrentIndex(i) # Save the currently selected join layer joinlayerid = self.joinlayerid # Add the layers to the join layers combobox self.joinVectorLayer.clear() for layerdescription in layerslist: self.joinVectorLayer.addItem(layerdescription[0], layerdescription[1]) # Set the previous selection for the join layer for i in range(self.joinVectorLayer.count()): if self.joinVectorLayer.itemData(i) == joinlayerid: self.joinVectorLayer.setCurrentIndex(i) self.layerlistchanging = False self.updateui() # End of layerlistchanged def updateui(self): """Do the necessary updates after a layer selection has been changed.""" # if self.layerlistchanged: # return # Update the output dataset name self.outputDataset.setText(self.inputVectorLayer.currentText() + '_' + self.joinVectorLayer.currentText()) # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Enable the OK button (if layers are OK) if inputlayer is not None and joinlayer is not None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) # Check the geometry type of the input layer and set # user interface options accordingly if inputlayer is not None: wkbType = inputlayer.wkbType() geomType = inputlayer.geometryType() # not used yet joinwkbType = QgsWkbTypes.Unknown joingeomType = QgsWkbTypes.UnknownGeometry # not used yet if joinlayer is not None: joinwkbType = joinlayer.wkbType() joingeomType = joinlayer.geometryType() # If the input layer is not a point layer, allow choosing # approximate geometry (centroid) if wkbType == QgsWkbTypes.Point or wkbType == QgsWkbTypes.Point25D: # Input layer is a simple point layer and can not # be approximated self.approximate_input_geom_cb.blockSignals(True) self.approximate_input_geom_cb.setCheckState(Qt.Unchecked) self.approximate_input_geom_cb.setVisible(False) self.approximate_input_geom_cb.blockSignals(False) else: # Input layer is not a point layer, so approximation # is possible self.approximate_input_geom_cb.blockSignals(True) self.approximate_input_geom_cb.setVisible(True) self.approximate_input_geom_cb.blockSignals(False) # Update the use index checkbox if ((wkbType == QgsWkbTypes.LineString or wkbType == QgsWkbTypes.LineString25D or wkbType == QgsWkbTypes.Polygon or wkbType == QgsWkbTypes.Polygon25D) and not self.approximate_input_geom_cb.isChecked()): # The input layer is a line or polygong layer that # is not approximated, so the user is allowed to # choose not to use the spatial index (not very useful!) if not self.use_index_nonpoint_cb.isVisible(): self.use_index_nonpoint_cb.blockSignals(True) self.use_index_nonpoint_cb.setCheckState(Qt.Checked) self.use_index_nonpoint_cb.setVisible(True) self.use_index_nonpoint_cb.blockSignals(False) else: # The input layer is either a point approximation # or it is a point layer (or some kind of # multigeometry!), anyway we won't allow the user to # choose not to use a spatial index self.use_index_nonpoint_cb.blockSignals(True) self.use_index_nonpoint_cb.setCheckState(Qt.Unchecked) self.use_index_nonpoint_cb.setVisible(False) self.use_index_nonpoint_cb.blockSignals(False) # This does not work!!???? # Update the use index approximation checkbox: if (((wkbType == QgsWkbTypes.Point) or (wkbType == QgsWkbTypes.Point25D) or self.approximate_input_geom_cb.isChecked()) and not (joinwkbType == QgsWkbTypes.Point or joinwkbType == QgsWkbTypes.Point25D)): # For non-point join layers and point input layers, # the user is allowed to choose an approximation (the # index geometry) to be used for the join geometry in # the join. self.use_indexapprox_cb.setVisible(True) else: # For point join layers, and non-point, # non-point-approximated input layers, the user is # not allowed to choose an approximation (the index # geometry) to be used for the join geometry in the # join. self.use_indexapprox_cb.blockSignals(True) self.use_indexapprox_cb.setCheckState(Qt.Unchecked) self.use_indexapprox_cb.setVisible(False) self.use_indexapprox_cb.blockSignals(False) # Update the exclude containing polygon checkbox: if ((wkbType == QgsWkbTypes.Point or wkbType == QgsWkbTypes.Point25D or self.approximate_input_geom_cb.isChecked()) and (joinwkbType == QgsWkbTypes.Polygon or joinwkbType == QgsWkbTypes.Polygon25D) and (not self.use_indexapprox_cb.isChecked())): # For polygon join layers and point input layers, # the user is allowed to choose to exclude the # containing polygon in the join. self.exclude_containing_poly_cb.blockSignals(True) self.exclude_containing_poly_cb.setVisible(True) self.exclude_containing_poly_cb.blockSignals(False) else: self.exclude_containing_poly_cb.blockSignals(True) self.exclude_containing_poly_cb.setCheckState(Qt.Unchecked) self.exclude_containing_poly_cb.setVisible(False) self.exclude_containing_poly_cb.blockSignals(False) # Set the default background (white) for the distance field name self.distancefieldname.setStyleSheet("background:#fff;") # Check if the distance field name already is used inputfields = inputlayer.fields().toList() for infield in inputfields: if infield.name() == self.distancefieldname.text(): self.distancefieldname.setStyleSheet("background:#f00;") self.showInfo( "Distance field name conflict in input layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) break if joinlayer is not None: joinfields = joinlayer.fields().toList() for joinfield in joinfields: if (self.joinPrefix.text() + joinfield.name() == self.distancefieldname.text()): self.distancefieldname.setStyleSheet( "background:#f00;") self.showInfo( "Distance field name conflict in join layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) break else: # No input layer defined, so options are disabled self.approximate_input_geom_cb.setVisible(False) self.use_indexapprox_cb.setVisible(False) self.use_index_nonpoint_cb.setVisible(False) # End of updateui def getwkbtext(self, number): if number == QgsWkbTypes.Unknown: return "Unknown" elif number == QgsWkbTypes.Point: return "Point" elif number == QgsWkbTypes.LineString: return "LineString" elif number == QgsWkbTypes.Polygon: return "Polygon" elif number == QgsWkbTypes.MultiPoint: return "MultiPoint" elif number == QgsWkbTypes.MultiLineString: return "MultiLineString" elif number == QgsWkbTypes.MultiPolygon: return "MultiPolygon" elif number == QgsWkbTypes.NoGeometry: return "NoGeometry" elif number == QgsWkbTypes.Point25D: return "Point25D" elif number == QgsWkbTypes.LineString25D: return "LineString25D" elif number == QgsWkbTypes.Polygon25D: return "Polygon25D" elif number == QgsWkbTypes.MultiPoint25D: return "MultiPoint25D" elif number == QgsWkbTypes.MultiLineString25D: return "MultiLineString25D" elif number == QgsWkbTypes.MultiPolygon25D: return "MultiPolygon25D" else: showError('Unknown or invalid geometry type: ' + str(number)) return "Don't know" # End of getwkbtext def killWorker(self): """Kill the worker thread.""" # if self.worker is not None: # self.showInfo(self.tr('Killing worker')) # self.worker.kill() def showError(self, text): """Show an error.""" self.iface.messageBar().pushMessage(self.tr('Error'), text, level=Qgis.Critical, duration=3) QgsMessageLog.logMessage('Error: ' + text, self.NNJOIN, Qgis.Critical) def showWarning(self, text): """Show a warning.""" self.iface.messageBar().pushMessage(self.tr('Warning'), text, level=Qgis.Warning, duration=2) QgsMessageLog.logMessage('Warning: ' + text, self.NNJOIN, Qgis.Warning) def showInfo(self, text): """Show info.""" self.iface.messageBar().pushMessage(self.tr('Info'), text, level=Qgis.Info, duration=2) QgsMessageLog.logMessage('Info: ' + text, self.NNJOIN, Qgis.Info) def help(self): QDesktopServices.openUrl(QUrl.fromLocalFile( self.plugin_dir + "/help/html/index.html")) # showPluginHelp(None, "help/html/index") def tr(self, message): """Get the translation for a string using Qt translation API. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ return QCoreApplication.translate('NNJoinDialog', message) # Implement the accept method to avoid exiting the dialog when # starting the work def accept(self): """Accept override.""" pass # Implement the reject method to have the possibility to avoid # exiting the dialog when cancelling def reject(self): """Reject override.""" # exit the dialog QDialog.reject(self)
class SimulationResults(uicls, basecls): """Dialog with methods for handling simulations results.""" PROGRESS_COLUMN_IDX = 3 def __init__(self, plugin_dock, parent=None): super().__init__(parent) self.setupUi(self) self.plugin_dock = plugin_dock self.api_client = self.plugin_dock.threedi_api self.download_results_thread = None self.download_worker = None self.finished_simulations = {} self.tv_model = None self.last_progress_item = None self.setup_view_model() self.plugin_dock.simulations_progresses_sentinel.progresses_fetched.connect(self.update_finished_list) self.pb_cancel.clicked.connect(self.close) self.pb_download.clicked.connect(self.download_results) self.tv_finished_sim_tree.selectionModel().selectionChanged.connect(self.toggle_download_results) def setup_view_model(self): """Setting up model and columns for TreeView.""" self.tv_model = QStandardItemModel(0, 4) delegate = DownloadProgressDelegate(self.tv_finished_sim_tree) self.tv_finished_sim_tree.setItemDelegateForColumn(self.PROGRESS_COLUMN_IDX, delegate) self.tv_model.setHorizontalHeaderLabels(["Simulation name", "User", "Expires", "Download progress"]) self.tv_finished_sim_tree.setModel(self.tv_model) def toggle_download_results(self): """Toggle download if any simulation is selected.""" if self.download_results_thread is None: selection_model = self.tv_finished_sim_tree.selectionModel() if selection_model.hasSelection(): self.pb_download.setEnabled(True) else: self.pb_download.setDisabled(True) def add_finished_simulation_to_model(self, simulation, status): """Method for adding simulation to the model.""" sim_id = simulation.id sim_name_item = QStandardItem(f"{simulation.name} ({sim_id})") sim_name_item.setData(sim_id, Qt.UserRole) user_item = QStandardItem(simulation.user) delta = relativedelta(status.created, ThreediCalls.EXPIRATION_TIME) expires_item = QStandardItem(f"{delta.days} day(s)") progress_item = QStandardItem() progress_item.setData(-1, Qt.UserRole) self.tv_model.insertRow(0, [sim_name_item, user_item, expires_item, progress_item]) self.finished_simulations[sim_id] = simulation def update_finished_list(self, progresses): """Update finished simulations list.""" for sim_id, (sim, status, progress) in progresses.items(): status_name = status.name if status_name != "finished": continue if sim_id not in self.finished_simulations: self.add_finished_simulation_to_model(sim, status) def on_download_progress_update(self, percentage): """Update download progress bar.""" self.last_progress_item.setData(percentage, Qt.UserRole) if percentage == 0: row = self.last_progress_item.index().row() name_text = self.tv_model.item(row, 0).text() msg = f"Downloading results of {name_text} started!" self.plugin_dock.communication.bar_info(msg) def on_download_finished_success(self, msg): """Reporting finish successfully status and closing download thread.""" self.plugin_dock.communication.bar_info(msg, log_text_color=Qt.darkGreen) self.download_results_thread.quit() self.download_results_thread.wait() self.download_results_thread = None self.download_worker = None self.toggle_download_results() def on_download_finished_failed(self, msg): """Reporting failure and closing download thread.""" self.plugin_dock.communication.bar_error(msg, log_text_color=Qt.red) self.download_results_thread.quit() self.download_results_thread.wait() self.download_results_thread = None self.download_worker = None self.toggle_download_results() def terminate_download_thread(self): """Forcing termination of download background thread.""" if self.download_results_thread is not None and self.download_results_thread.isRunning(): self.plugin_dock.communication.bar_info("Terminating download thread.") self.download_results_thread.terminate() self.plugin_dock.communication.bar_info("Waiting for download thread termination.") self.download_results_thread.wait() self.plugin_dock.communication.bar_info("Download worker terminated.") self.download_results_thread = None self.download_worker = None def pick_results_destination_dir(self): """Pick folder where results will be written to.""" working_dir = self.plugin_dock.plugin_settings.working_dir last_folder = QSettings().value("threedi/last_results_folder", working_dir, type=str) directory = QFileDialog.getExistingDirectory(self, "Select Results Directory", last_folder) if len(directory) == 0: return None QSettings().setValue("threedi/last_results_folder", directory) return directory def download_results(self): """Download simulation results files.""" current_index = self.tv_finished_sim_tree.currentIndex() if not current_index.isValid(): return working_dir = self.plugin_dock.plugin_settings.working_dir local_schematisations = list_local_schematisations(working_dir) try: current_row = current_index.row() name_item = self.tv_model.item(current_row, 0) sim_id = name_item.data(Qt.UserRole) simulation = self.finished_simulations[sim_id] simulation_name = simulation.name.replace(" ", "_") simulation_model_id = int(simulation.threedimodel_id) tc = ThreediCalls(self.plugin_dock.threedi_api) try: model_3di = tc.fetch_3di_model(simulation_model_id) gridadmin_downloads = tc.fetch_3di_model_gridadmin_download(simulation_model_id) if model_3di.schematisation_id: model_schematisation_id = model_3di.schematisation_id model_schematisation_name = model_3di.schematisation_name model_revision_number = model_3di.revision_number try: local_schematisation = local_schematisations[model_schematisation_id] except KeyError: local_schematisation = LocalSchematisation( working_dir, model_schematisation_id, model_schematisation_name, create=True ) try: local_revision = local_schematisation.revisions[model_revision_number] except KeyError: local_revision = LocalRevision(local_schematisation, model_revision_number) local_revision.make_revision_structure() results_dir = local_revision.results_dir else: warn_msg = ( "The 3Di model to which these results belong was uploaded with Tortoise and does not " "belong to any schematisation. Therefore, it cannot be determined to which " "schematisation the results should be downloaded.\n\nPlease select a directory to save " "the result files to." ) self.plugin_dock.communication.show_warn(warn_msg) results_dir = self.pick_results_destination_dir() if not results_dir: self.plugin_dock.communication.show_warn(warn_msg) return except ApiException as e: if e.status == 404: warn_msg = ( "The 3Di model to which these results belong is owned by an organisation for which " "you do not have sufficient rights. Therefore, you cannot download the computational " "grid (gridadmin.h5) and it cannot be determined to which schematisation the results " "should be downloaded.\n\nContact the servicedesk to obtain access rights to the " "organisation that owns the 3Di model.\n\nPlease select a directory to save the result" " files to." ) self.plugin_dock.communication.show_warn(warn_msg) results_dir = self.pick_results_destination_dir() if not results_dir: return gridadmin_downloads = None else: raise e simulation_subdirectory = os.path.join(results_dir, f"sim_{sim_id}_{simulation_name}") downloads = tc.fetch_simulation_downloads(sim_id) if gridadmin_downloads is not None: downloads.append(gridadmin_downloads) downloads.sort(key=lambda x: x[-1].size) self.last_progress_item = self.tv_model.item(current_row, self.PROGRESS_COLUMN_IDX) except ApiException as e: error_msg = extract_error_message(e) self.plugin_dock.communication.show_error(error_msg) return except Exception as e: error_msg = f"Error: {e}" self.plugin_dock.communication.show_error(error_msg) return self.pb_download.setDisabled(True) self.download_results_thread = QThread() self.download_worker = DownloadProgressWorker(simulation, downloads, simulation_subdirectory) self.download_worker.moveToThread(self.download_results_thread) self.download_worker.thread_finished.connect(self.on_download_finished_success) self.download_worker.download_failed.connect(self.on_download_finished_failed) self.download_worker.download_progress.connect(self.on_download_progress_update) self.download_results_thread.started.connect(self.download_worker.run) self.download_results_thread.start()
class OpenTripPlannerPlugin(): """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 # 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', 'OpenTripPlannerPlugin_{}.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'&OpenTripPlanner Plugin') # 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('OpenTripPlannerPlugin', 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.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/otp_plugin/icon.png' self.add_action(icon_path, text=self.tr(u'OpenTripPlanner Plugin'), 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.removePluginMenu(self.tr(u'&OpenTripPlanner Plugin'), action) self.iface.removeToolBarIcon(action) def isochronesStartWorker(self): # method to start the worker thread if not self.gf.isochrones_selectedlayer: # dont execute if no layer is selected QgsMessageLog.logMessage( "Warning! No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.isochrones_selectedlayer.featureCount() == 0: QgsMessageLog.logMessage( "Warning! Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return isochrones_memorylayer_vl = QgsVectorLayer( "MultiPolygon?crs=epsg:4326", "Isochrones", "memory") # Create temporary polygon layer (output file) self.isochrones_thread = QThread() self.isochrones_worker = OpenTripPlannerPluginIsochronesWorker( self.dlg, self.iface, self.gf, isochrones_memorylayer_vl) # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html self.isochrones_worker.moveToThread( self.isochrones_thread) # move Worker-Class to a thread # Connect signals and slots: self.isochrones_thread.started.connect(self.isochrones_worker.run) self.isochrones_worker.isochrones_finished.connect( self.isochrones_thread.quit) self.isochrones_worker.isochrones_finished.connect( self.isochrones_worker.deleteLater) self.isochrones_thread.finished.connect( self.isochrones_thread.deleteLater) self.isochrones_worker.isochrones_progress.connect( self.isochronesReportProgress) self.isochrones_worker.isochrones_finished.connect( self.isochronesFinished) self.isochrones_thread.start() # finally start the thread # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress self.gf.disableIsochronesGui() self.gf.disableGeneralSettingsGui() self.isochrones_thread.finished.connect( lambda: self.gf.enableIsochronesGui()) self.isochrones_thread.finished.connect( lambda: self.gf.enableGeneralSettingsGui()) def isochronesKillWorker(self): # method to kill/cancel the worker thread # print('pushed cancel') # debugging # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html try: # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except self.isochrones_worker.stop( ) # call the stop method in worker class to break the work-loop so we can quit the thread if self.isochrones_thread.isRunning( ): # check if a thread is running # print('pushed cancel, thread is running, trying to cancel') # debugging self.isochrones_thread.requestInterruption() self.isochrones_thread.exit( ) # Tells the thread’s event loop to exit with a return code. self.isochrones_thread.quit( ) # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0). self.isochrones_thread.wait( ) # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait except: self.dlg.Isochrones_ProgressBar.setValue(0) self.dlg.Isochrones_StatusBox.setText('') def isochronesReportProgress( self, progress, status): # method to report the progress to gui self.dlg.Isochrones_ProgressBar.setValue( progress) # set the current progress in progress bar self.dlg.Isochrones_StatusBox.setText(status) def isochronesFinished( self, isochrones_resultlayer, isochrones_state, unique_errors="", runtime="00:00:00 (unknown)" ): # method to interact with gui when thread is finished or canceled QgsProject.instance().addMapLayer( isochrones_resultlayer) # Show resultlayer in project # isochrones_state is indicating different states of the thread/result as integer if unique_errors: self.iface.messageBar().pushMessage( "Warning", " Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, level=Qgis.Warning, duration=12) QgsMessageLog.logMessage( "Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, Qgis.Warning) if isochrones_state == 0: self.iface.messageBar().pushMessage( "Warning", " Run-Method was never executed.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) elif isochrones_state == 1: self.iface.messageBar().pushMessage( "Done!", " Isochrones job finished after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif isochrones_state == 2: self.iface.messageBar().pushMessage( "Done!", " Isochrones job canceled after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif isochrones_state == 3: self.iface.messageBar().pushMessage( "Warning", " No Isochrones to create - Check your settings and retry.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif isochrones_state == 99: self.iface.messageBar().pushMessage( "Debugging", " Just having some debugging fun :)", MESSAGE_CATEGORY, level=Qgis.Info, duration=6) else: self.iface.messageBar().pushMessage( "Warning", " Unknown error occurred during execution.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) def aggregated_isochronesStartWorker( self): # method to start the worker thread if not self.gf.aggregated_isochrones_selectedlayer: # dont execute if no layer is selected QgsMessageLog.logMessage( "Warning! No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " No inputlayer selected. Choose an inputlayer and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.aggregated_isochrones_selectedlayer.featureCount() == 0: QgsMessageLog.logMessage( "Warning! Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " Inputlayer is empty. Add some features and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return aggregated_isochrones_memorylayer_vl = QgsVectorLayer( "MultiPolygon?crs=epsg:4326", "AggregatedIsochrones", "memory") # Create temporary polygon layer (output file) self.aggregated_isochrones_thread = QThread() self.aggregated_isochrones_worker = OpenTripPlannerPluginAggregatedIsochronesWorker( self.dlg, self.iface, self.gf, aggregated_isochrones_memorylayer_vl) # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html self.aggregated_isochrones_worker.moveToThread( self.aggregated_isochrones_thread) # move Worker-Class to a thread # Connect signals and slots: self.aggregated_isochrones_thread.started.connect( self.aggregated_isochrones_worker.run) self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect( self.aggregated_isochrones_thread.quit) self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect( self.aggregated_isochrones_worker.deleteLater) self.aggregated_isochrones_thread.finished.connect( self.aggregated_isochrones_thread.deleteLater) self.aggregated_isochrones_worker.aggregated_isochrones_progress.connect( self.aggregated_isochronesReportProgress) self.aggregated_isochrones_worker.aggregated_isochrones_finished.connect( self.aggregated_isochronesFinished) self.aggregated_isochrones_thread.start() # finally start the thread # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress self.gf.disableAggregatedIsochronesGui() self.gf.disableGeneralSettingsGui() self.aggregated_isochrones_thread.finished.connect( lambda: self.gf.enableAggregatedIsochronesGui()) self.aggregated_isochrones_thread.finished.connect( lambda: self.gf.enableGeneralSettingsGui()) def aggregated_isochronesKillWorker( self): # method to kill/cancel the worker thread # print('pushed cancel') # debugging # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html try: # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except self.aggregated_isochrones_worker.stop( ) # call the stop method in worker class to break the work-loop so we can quit the thread if self.aggregated_isochrones_thread.isRunning( ): # check if a thread is running # print('pushed cancel, thread is running, trying to cancel') # debugging self.aggregated_isochrones_thread.requestInterruption() self.aggregated_isochrones_thread.exit( ) # Tells the thread’s event loop to exit with a return code. self.aggregated_isochrones_thread.quit( ) # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0). self.aggregated_isochrones_thread.wait( ) # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait except: self.dlg.AggregatedIsochrones_ProgressBar.setValue(0) self.dlg.AggregatedIsochrones_StatusBox.setText('') def aggregated_isochronesReportProgress( self, progress, status): # method to report the progress to gui self.dlg.AggregatedIsochrones_ProgressBar.setValue( progress) # set the current progress in progress bar self.dlg.AggregatedIsochrones_StatusBox.setText(status) def aggregated_isochronesFinished( self, aggregated_isochrones_resultlayer, aggregated_isochrones_state, unique_errors="", runtime="00:00:00 (unknown)" ): # method to interact with gui when thread is finished or canceled QgsProject.instance().addMapLayer( aggregated_isochrones_resultlayer) # Show resultlayer in project # aggregated_isochrones_state is indicating different states of the thread/result as integer if unique_errors: self.iface.messageBar().pushMessage( "Warning", " Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors, MESSAGE_CATEGORY, level=Qgis.Warning, duration=12) QgsMessageLog.logMessage( "Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors, MESSAGE_CATEGORY, Qgis.Warning) if aggregated_isochrones_state == 0: self.iface.messageBar().pushMessage( "Warning", " Run-Method was never executed.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) elif aggregated_isochrones_state == 1: self.iface.messageBar().pushMessage( "Done!", " Isochrones job finished after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif aggregated_isochrones_state == 2: self.iface.messageBar().pushMessage( "Done!", " Isochrones job canceled after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif aggregated_isochrones_state == 3: self.iface.messageBar().pushMessage( "Warning", " No Isochrones to create - Check your settings and retry.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif aggregated_isochrones_state == 4: self.iface.messageBar().pushMessage( "Warning", " There is something wrong with your DateTime-Settings, check them and try again.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif aggregated_isochrones_state == 99: self.iface.messageBar().pushMessage( "Debugging", " Just having some debugging fun :)", MESSAGE_CATEGORY, level=Qgis.Info, duration=6) else: self.iface.messageBar().pushMessage( "Warning", " Unknown error occurred during execution.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) def routesStartWorker(self): # method to start the worker thread if not self.gf.routes_selectedlayer_source or not self.gf.routes_selectedlayer_target: QgsMessageLog.logMessage( "Warning! No inputlayer selected. Choose your inputlayers and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " No sourcelayer or no targetlayer selected. Choose your inputlayers and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.routes_selectedlayer_source.fields().count( ) == 0 or self.gf.routes_selectedlayer_target.fields().count() == 0: QgsMessageLog.logMessage( "Warning! Inputlayer has no fields. Script wont work until you add at least one dummy ID-Field.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " Inputlayer has no fields - Add at least a dummy-id field.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return if self.gf.routes_selectedlayer_source.featureCount( ) == 0 or self.gf.routes_selectedlayer_target.featureCount() == 0: QgsMessageLog.logMessage( "Warning! One or both inputlayers are empty. Add some features and try again.", MESSAGE_CATEGORY, Qgis.Critical) self.iface.messageBar().pushMessage( "Warning", " One or both inputlayers are empty. Add some features and try again.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) return routes_memorylayer_vl = QgsVectorLayer( "LineString?crs=epsg:4326", "Routes", "memory") # Create temporary polygon layer (output file) self.routes_thread = QThread() self.routes_worker = OpenTripPlannerPluginRoutesWorker( self.dlg, self.iface, self.gf, routes_memorylayer_vl) # see https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis # and https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html self.routes_worker.moveToThread( self.routes_thread) # move Worker-Class to a thread # Connect signals and slots: self.routes_thread.started.connect(self.routes_worker.run) self.routes_worker.routes_finished.connect(self.routes_thread.quit) self.routes_worker.routes_finished.connect( self.routes_worker.deleteLater) self.routes_thread.finished.connect(self.routes_thread.deleteLater) self.routes_worker.routes_progress.connect(self.routesReportProgress) self.routes_worker.routes_finished.connect(self.routesFinished) self.routes_thread.start() # finally start the thread # Disable/Enable GUI elements to prevent them from beeing used while worker threads are running and accidentially changing settings during progress self.gf.disableRoutesGui() self.gf.disableGeneralSettingsGui() self.routes_thread.finished.connect(lambda: self.gf.enableRoutesGui()) self.routes_thread.finished.connect( lambda: self.gf.enableGeneralSettingsGui()) def routesKillWorker(self): # method to kill/cancel the worker thread # print('pushed cancel') # debugging # see https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html try: # to prevent a Python error when the cancel button has been clicked but no thread is running use try/except self.routes_worker.stop( ) # call the stop method in worker class to break the work-loop so we can quit the thread if self.routes_thread.isRunning(): # check if a thread is running # print('pushed cancel, thread is running, trying to cancel') # debugging self.routes_thread.requestInterruption() self.routes_thread.exit( ) # Tells the thread’s event loop to exit with a return code. self.routes_thread.quit( ) # Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling exit (0). self.routes_thread.wait( ) # Blocks the thread until https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html#PySide6.QtCore.PySide6.QtCore.QThread.wait except: self.dlg.Routes_ProgressBar.setValue(0) self.dlg.Routes_StatusBox.setText('') def routesReportProgress(self, progress, status): # method to report the progress to gui self.dlg.Routes_ProgressBar.setValue( progress) # set the current progress in progress bar self.dlg.Routes_StatusBox.setText(status) def routesFinished( self, routes_resultlayer, routes_state, unique_errors="", runtime="00:00:00 (unknown)" ): # method to interact with gui when thread is finished or canceled QgsProject.instance().addMapLayer( routes_resultlayer) # Show resultlayer in project # routes_state is indicating different states of the thread/result as integer if unique_errors: self.iface.messageBar().pushMessage( "Warning", " Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, level=Qgis.Warning, duration=12) QgsMessageLog.logMessage( "Errors occurred. Check the resultlayer for details. The errors were: " + unique_errors + " - Created dummy geometries at coordinate 0,0 on error features", MESSAGE_CATEGORY, Qgis.Warning) if routes_state == 0: self.iface.messageBar().pushMessage( "Warning", " Run-Method was never executed.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) elif routes_state == 1: self.iface.messageBar().pushMessage("Done!", " Routes job finished after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif routes_state == 2: self.iface.messageBar().pushMessage("Done!", " Routes job canceled after " + runtime, MESSAGE_CATEGORY, level=Qgis.Success, duration=6) elif routes_state == 3: self.iface.messageBar().pushMessage( "Warning", " No Routes to create / no matching attributes - Check your settings and retry.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6) elif routes_state == 99: self.iface.messageBar().pushMessage( "Debugging", " Just having some debugging fun :)", MESSAGE_CATEGORY, level=Qgis.Info, duration=6) else: self.iface.messageBar().pushMessage( "Warning", " Unknown error occurred during execution.", MESSAGE_CATEGORY, level=Qgis.Critical, duration=6) 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.first_start = False self.dlg = OpenTripPlannerPluginDialog() self.gf = OpenTripPlannerPluginGeneralFunctions( self.dlg, self.iface) # Calling maplayer selection on first startup to load layers into QgsMapLayerComboBox and initialize QgsOverrideButton stuff so selections can be done without actually using the QgsMapLayerComboBox (related to currentIndexChanged.connect(self.isochrones_maplayerselection) below) self.gf.routes_maplayerselection() self.gf.isochrones_maplayerselection() self.gf.aggregated_isochrones_maplayerselection() # Execute Main-Functions on Click: Placing them here prevents them from beeing executed multiple times, see https://gis.stackexchange.com/a/137161/107424 self.dlg.Isochrones_RequestIsochrones.clicked.connect( lambda: self.isochronesStartWorker() ) #Call the start worker method self.dlg.Isochrones_Cancel.clicked.connect( lambda: self.isochronesKillWorker()) self.dlg.AggregatedIsochrones_RequestIsochrones.clicked.connect( lambda: self.aggregated_isochronesStartWorker() ) #Call the start worker method self.dlg.AggregatedIsochrones_Cancel.clicked.connect( lambda: self.aggregated_isochronesKillWorker()) self.dlg.Routes_RequestRoutes.clicked.connect( lambda: self.routesStartWorker()) self.dlg.Routes_Cancel.clicked.connect( lambda: self.routesKillWorker()) # Calling Functions on button click self.dlg.GeneralSettings_CheckServerStatus.clicked.connect( self.gf.check_server_status) self.dlg.GeneralSettings_Save.clicked.connect( self.gf.store_general_variables ) #Call store_general_variables function when clicking on save button self.dlg.GeneralSettings_Restore.clicked.connect( self.gf.restore_general_variables) self.dlg.Isochrones_SaveSettings.clicked.connect( self.gf.store_isochrone_variables) self.dlg.Isochrones_RestoreDefaultSettings.clicked.connect( self.gf.restore_isochrone_variables) self.dlg.Isochrones_Now.clicked.connect( self.gf.set_datetime_now_isochrone) self.dlg.AggregatedIsochrones_SaveSettings.clicked.connect( self.gf.store_aggregated_isochrone_variables) self.dlg.AggregatedIsochrones_RestoreDefaultSettings.clicked.connect( self.gf.restore_aggregated_isochrone_variables) self.dlg.AggregatedIsochrones_Now.clicked.connect( self.gf.set_datetime_now_aggregated_isochrone) self.dlg.Routes_SaveSettings.clicked.connect( self.gf.store_route_variables) self.dlg.Routes_RestoreDefaultSettings.clicked.connect( self.gf.restore_route_variables) self.dlg.Routes_Now.clicked.connect(self.gf.set_datetime_now_route) # Calling Functions to update layer stuff when layerselection has changed self.dlg.Isochrones_SelectInputLayer.currentIndexChanged.connect( self.gf.isochrones_maplayerselection ) # Call function isochrones_maplayerselection to update all selection related stuff when selection has been changed self.dlg.AggregatedIsochrones_SelectInputLayer.currentIndexChanged.connect( self.gf.aggregated_isochrones_maplayerselection) self.dlg.Routes_SelectInputLayer_Source.currentIndexChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_SelectInputLayer_Target.currentIndexChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_SelectInputField_Source.currentIndexChanged.connect( self.gf.routes_maplayerselection) # or "fieldChanged"? self.dlg.Routes_SelectInputField_Target.currentIndexChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_DataDefinedLayer_Source.stateChanged.connect( self.gf.routes_maplayerselection) self.dlg.Routes_DataDefinedLayer_Target.stateChanged.connect( self.gf.routes_maplayerselection) # Setting GUI stuff for startup every time the plugin is opened self.dlg.Isochrones_Date.setDateTime( QtCore.QDateTime.currentDateTime() ) # Set Dateselection to today on restart or firststart, only functional if never used save settings, otherwise overwritten by read_route_variables() self.dlg.AggregatedIsochrones_FromDateTime.setDateTime( QtCore.QDateTime.currentDateTime()) self.dlg.AggregatedIsochrones_ToDateTime.setDateTime( QtCore.QDateTime.currentDateTime()) self.dlg.Routes_Date.setDateTime(QtCore.QDateTime.currentDateTime()) self.dlg.Isochrones_ProgressBar.setValue( 0) # Set Progressbar to 0 on restart or first start self.dlg.AggregatedIsochrones_ProgressBar.setValue(0) self.dlg.Routes_ProgressBar.setValue(0) self.dlg.GeneralSettings_ServerStatusResult.setText( "Serverstatus Unknown") self.dlg.GeneralSettings_ServerStatusResult.setStyleSheet( "background-color: white; color: black ") # Functions to execute every time the plugin is opened self.gf.read_general_variables( ) #Run Read-Stored-Variables-Function on every start self.gf.read_isochrone_variables() self.gf.read_aggregated_isochrone_variables() self.gf.read_route_variables() # 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. QgsMessageLog.logMessage( "OpenTripPlanner Plugin is already running! Close it before, if you wish to restart it.", MESSAGE_CATEGORY, Qgis.Warning) self.iface.messageBar().pushMessage( "Error", "OpenTripPlanner Plugin is already running! Close it before, if you wish to restart it.", MESSAGE_CATEGORY, level=Qgis.Warning, duration=6)
class ThreediDockWidget(QtWidgets.QDockWidget, FORM_CLASS): closingPlugin = pyqtSignal() def __init__(self, iface, plugin_settings, parent=None): """Constructor.""" super().__init__(parent) self.setupUi(self) self.iface = iface self.plugin_settings = plugin_settings self.communication = UICommunication(self.iface, "3Di Models and Simulations", self.lv_log) self.simulations_progresses_thread = None self.simulations_progresses_sentinel = None self.threedi_api = None self.current_user = None self.current_user_full_name = None self.organisations = {} self.current_local_schematisation = None self.build_options = BuildOptions(self) self.simulation_overview_dlg = None self.simulation_results_dlg = None self.upload_dlg = None self.btn_log_in_out.clicked.connect(self.on_log_in_log_out) self.btn_load_schematisation.clicked.connect( self.build_options.load_local_schematisation) self.btn_load_revision.clicked.connect( self.build_options.load_local_schematisation) self.btn_new.clicked.connect(self.build_options.new_schematisation) self.btn_download.clicked.connect( self.build_options.download_schematisation) self.btn_upload.clicked.connect(self.show_upload_dialog) self.btn_simulate.clicked.connect(self.show_simulation_overview) self.btn_results.clicked.connect(self.show_simulation_results) self.btn_manage.clicked.connect(self.on_manage) self.plugin_settings.settings_changed.connect(self.on_log_out) set_icon(self.btn_new, "new.svg") set_icon(self.btn_download, "download.svg") set_icon(self.btn_upload, "upload.svg") set_icon(self.btn_simulate, "api.svg") set_icon(self.btn_results, "results.svg") set_icon(self.btn_manage, "manage.svg") set_icon(self.btn_log_in_out, "arrow.svg") set_icon(self.btn_load_schematisation, "arrow.svg") set_icon(self.btn_load_revision, "arrow.svg") def closeEvent(self, event): if self.threedi_api is not None: self.on_log_out() self.current_local_schematisation = None self.update_schematisation_view() self.closingPlugin.emit() event.accept() def on_log_in_log_out(self): """Trigger log-in or log-out action.""" if self.threedi_api is None: self.on_log_in() else: self.on_log_out() def on_log_in(self): """Handle logging-in.""" log_in_dialog = LogInDialog(self) log_in_dialog.show() QTimer.singleShot(10, log_in_dialog.log_in_threedi) log_in_dialog.exec_() if log_in_dialog.LOGGED_IN: self.threedi_api = log_in_dialog.threedi_api self.current_user = log_in_dialog.user self.current_user_full_name = log_in_dialog.user_full_name self.organisations = log_in_dialog.organisations self.initialize_authorized_view() def on_log_out(self): """Handle logging-out.""" if self.simulations_progresses_thread is not None: self.stop_fetching_simulations_progresses() self.simulation_overview_dlg.model_selection_dlg.unload_cached_layers( ) self.simulation_overview_dlg = None if self.simulation_results_dlg is not None: self.simulation_results_dlg.terminate_download_thread() self.simulation_results_dlg = None if self.upload_dlg: self.upload_dlg.hide() self.upload_dlg = None self.threedi_api = None self.current_user = None self.current_user_full_name = None self.organisations.clear() self.label_user.setText("-") set_icon(self.btn_log_in_out, "arrow.svg") self.btn_log_in_out.setToolTip("Log in") def update_schematisation_view(self): """Method for updating loaded schematisation labels.""" if self.current_local_schematisation: schema_name = self.current_local_schematisation.name schema_dir = self.current_local_schematisation.main_dir schema_label_text = f'<a href="file:///{schema_dir}">{schema_name}</a>' schema_tooltip = f"{schema_name}\n{schema_dir}" self.label_schematisation.setText(schema_label_text) self.label_schematisation.setOpenExternalLinks(True) self.label_schematisation.setToolTip(schema_tooltip) if self.current_local_schematisation.wip_revision: self.label_revision.setText( str(self.current_local_schematisation.wip_revision.number) or "") else: self.label_revision.setText("") else: self.label_schematisation.setText("") self.label_schematisation.setToolTip("") self.label_revision.setText("") def initialize_authorized_view(self): """Method for initializing processes after logging in 3Di API.""" set_icon(self.btn_log_in_out, "x.svg") self.btn_log_in_out.setToolTip("Log out") self.label_user.setText(self.current_user_full_name) self.initialize_simulations_progresses_thread() self.initialize_simulation_overview() self.initialize_simulation_results() def initialize_simulations_progresses_thread(self): """Initializing of the background thread.""" if self.simulations_progresses_thread is not None: self.terminate_fetching_simulations_progresses_thread() self.simulations_progresses_thread = QThread() username, personal_api_key = self.plugin_settings.get_3di_auth() self.simulations_progresses_sentinel = WSProgressesSentinel( self.threedi_api, self.plugin_settings.wss_url, personal_api_key) self.simulations_progresses_sentinel.moveToThread( self.simulations_progresses_thread) self.simulations_progresses_sentinel.thread_finished.connect( self.on_fetching_simulations_progresses_finished) self.simulations_progresses_sentinel.thread_failed.connect( self.on_fetching_simulations_progresses_failed) self.simulations_progresses_thread.started.connect( self.simulations_progresses_sentinel.run) self.simulations_progresses_thread.start() def stop_fetching_simulations_progresses(self): """Changing 'thread_active' flag inside background task that is fetching simulations progresses.""" if self.simulations_progresses_sentinel is not None: self.simulations_progresses_sentinel.stop_listening() def on_fetching_simulations_progresses_finished(self, msg): """Method for cleaning up background thread after it sends 'thread_finished'.""" self.communication.bar_info(msg) self.simulations_progresses_thread.quit() self.simulations_progresses_thread.wait() self.simulations_progresses_thread = None self.simulations_progresses_sentinel = None def on_fetching_simulations_progresses_failed(self, msg): """Reporting fetching progresses failure.""" self.communication.bar_error(msg, log_text_color=Qt.red) def terminate_fetching_simulations_progresses_thread(self): """Forcing termination of background thread if it's still running.""" if self.simulations_progresses_thread is not None and self.simulations_progresses_thread.isRunning( ): self.communication.bar_info( "Terminating fetching simulations progresses thread.") self.simulations_progresses_thread.terminate() self.communication.bar_info( "Waiting for fetching simulations progresses thread termination." ) self.simulations_progresses_thread.wait() self.communication.bar_info( "Fetching simulations progresses worker terminated.") self.simulations_progresses_thread = None self.simulations_progresses_sentinel = None def initialize_simulation_overview(self): """Initialization of the Simulation Overview window.""" self.simulation_overview_dlg = SimulationOverview(self) self.simulation_overview_dlg.label_user.setText( self.current_user_full_name) @api_client_required def show_simulation_overview(self): """Showing Simulation Overview with running simulations progresses.""" if self.simulation_overview_dlg is None: self.initialize_simulation_overview() self.simulation_overview_dlg.show() self.simulation_overview_dlg.raise_() self.simulation_overview_dlg.activateWindow() def initialize_simulation_results(self): """Initialization of the Simulations Results window.""" self.simulation_results_dlg = SimulationResults(self) @api_client_required def show_simulation_results(self): """Showing finished simulations.""" if self.simulation_results_dlg is None: self.initialize_simulation_results() self.simulation_results_dlg.show() self.simulation_results_dlg.raise_() self.simulation_results_dlg.activateWindow() def initialize_upload_status(self): """Initialization of the Upload Status dialog.""" self.upload_dlg = UploadOverview(self) @api_client_required def show_upload_dialog(self): """Show upload status dialog.""" if self.upload_dlg is None: self.initialize_upload_status() self.upload_dlg.show() self.upload_dlg.raise_() self.upload_dlg.activateWindow() @staticmethod def on_manage(): """Open 3Di management webpage.""" webbrowser.open("https://management.3di.live/")
class ResourceSharingDialog(QDialog, FORM_CLASS): TAB_ALL = 0 TAB_INSTALLED = 1 TAB_SETTINGS = 2 def __init__(self, parent=None, iface=None): """Constructor. :param parent: Optional widget to use as parent :type parent: QWidget :param iface: An instance of QGisInterface :type iface: QGisInterface """ super(ResourceSharingDialog, self).__init__(parent) self.setupUi(self) self.iface = iface # Reconfigure UI self.setModal(True) self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) self.button_install.setEnabled(False) self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Set up the "main menu" - QListWidgetItem # All collections icon_all = QIcon() icon_all.addFile(str(resources_path('img', 'plugin.svg')), QSize(), QIcon.Normal, QIcon.Off) item_all = QListWidgetItem() item_all.setIcon(icon_all) item_all.setText(self.tr('All collections')) # Installed collections icon_installed = QIcon() icon_installed.addFile( str(resources_path('img', 'plugin-installed.svg')), QSize(), QIcon.Normal, QIcon.Off) item_installed = QListWidgetItem() item_installed.setIcon(icon_installed) item_installed.setText(self.tr('Installed collections')) item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Settings / repositories icon_settings = QIcon() icon_settings.addFile(str(resources_path('img', 'settings.svg')), QSize(), QIcon.Normal, QIcon.Off) item_settings = QListWidgetItem() item_settings.setIcon(icon_settings) item_settings.setText(self.tr('Settings')) item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Add the items to the list widget self.menu_list_widget.addItem(item_all) self.menu_list_widget.addItem(item_installed) self.menu_list_widget.addItem(item_settings) # Init the message bar self.message_bar = QgsMessageBar(self) self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.vlayoutRightColumn.insertWidget(0, self.message_bar) # Progress dialog for long running processes self.progress_dialog = None # Init the repository manager dialog self.repository_manager = RepositoryManager() self.collection_manager = CollectionManager() # Collections list view self.collections_model = QStandardItemModel(0, 1) self.collections_model.sort(0, Qt.AscendingOrder) self.collection_proxy = CustomSortFilterProxyModel(self) self.collection_proxy.setSourceModel(self.collections_model) self.list_view_collections.setModel(self.collection_proxy) # Active selected collection self._selected_collection_id = None # Slots self.button_add.clicked.connect(self.add_repository) self.button_edit.clicked.connect(self.edit_repository) self.button_delete.clicked.connect(self.delete_repository) self.button_reload.clicked.connect(self.reload_repositories) self.menu_list_widget.currentRowChanged.connect(self.set_current_tab) self.list_view_collections.selectionModel().currentChanged.connect( self.on_list_view_collections_clicked) self.line_edit_filter.textChanged.connect(self.filter_collections) self.button_install.clicked.connect(self.install_collection) self.button_open.clicked.connect(self.open_collection) self.button_uninstall.clicked.connect(self.uninstall_collection) self.button_box.button(QDialogButtonBox.Help).clicked.connect( self.open_help) # Populate the repositories widget and collections list view self.populate_repositories_widget() self.reload_collections_model() def set_current_tab(self, index): """Set stacked widget based on the active tab. :param index: The index of the active widget (in the list widget). :type index: int """ # Clear message bar self.message_bar.clearWidgets() if index == (self.menu_list_widget.count() - 1): # Last menu entry - Settings self.stacked_menu_widget.setCurrentIndex(1) else: # Not settings, must be Collections (all or installed) if index == 1: # Installed collections self.collection_proxy.accepted_status = \ COLLECTION_INSTALLED_STATUS # Set the web view title = self.tr('Installed Collections') description = self.tr( 'On the left you see the list of all the ' 'installed collections.') else: # All collections (0) self.collection_proxy.accepted_status = COLLECTION_ALL_STATUS # Set the web view title = self.tr('All Collections') description = self.tr( 'On the left you see a list of all the collections ' 'that are available from the registered repositories.<br> ' 'Installed collections are emphasized (in <b>bold</b>).') context = { 'resources_path': str(resources_path()), 'title': title, 'description': description } self.web_view_details.setHtml( render_template('tab_description.html', context)) self.stacked_menu_widget.setCurrentIndex(0) def add_repository(self): """Open add repository dialog.""" dlg = ManageRepositoryDialog(self) if not dlg.exec_(): return for repoName, repo in self.repository_manager.directories.items(): if dlg.line_edit_url.text().strip() == repo['url']: self.message_bar.pushMessage( self.tr( 'Unable to add another repository with the same URL!'), Qgis.Warning, 5) return if dlg.line_edit_name.text().strip() == repoName: self.message_bar.pushMessage( self.tr('Repositories must have unique names!'), Qgis.Warning, 5) return repo_name = dlg.line_edit_name.text() repo_url = dlg.line_edit_url.text().strip() repo_auth_cfg = dlg.line_edit_auth_id.text().strip() if repo_name in self.repository_manager.directories: repo_name += '(2)' # Show progress dialog self.show_progress_dialog("Fetching repository's metadata") # Add repository try: status, adderror = self.repository_manager.add_directory( repo_name, repo_url, repo_auth_cfg) if status: self.message_bar.pushMessage( self.tr('Repository was successfully added'), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr('Unable to add repository: %s') % adderror, Qgis.Warning, 5) except Exception as e: self.message_bar.pushMessage(self.tr('%s') % e, Qgis.Warning, 5) finally: self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() # Deactivate edit and delete button self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def edit_repository(self): """Open edit repository dialog.""" selected_item = self.tree_repositories.currentItem() if selected_item: repo_name = selected_item.text(0) if not repo_name: return # Check if it is among the officially approved QGIS repositories settings = QgsSettings() settings.beginGroup(repo_settings_group()) if settings.value(repo_name + '/url') in \ self.repository_manager._online_directories.values(): self.message_bar.pushMessage( self.tr('You can not edit the official repositories!'), Qgis.Warning, 5) return dlg = ManageRepositoryDialog(self) dlg.line_edit_name.setText(repo_name) dlg.line_edit_url.setText( self.repository_manager.directories[repo_name]['url']) dlg.line_edit_auth_id.setText( self.repository_manager.directories[repo_name]['auth_cfg']) if not dlg.exec_(): return # Check if the changed URL is already present and that # the new repository name is unique new_url = dlg.line_edit_url.text().strip() old_url = self.repository_manager.directories[repo_name]['url'] new_name = dlg.line_edit_name.text().strip() for repoName, repo in self.repository_manager.directories.items(): if new_url == repo['url'] and (old_url != new_url): self.message_bar.pushMessage( self.tr('Unable to add another repository with the same ' 'URL!'), Qgis.Warning, 5) return if new_name == repoName and (repo_name != new_name): self.message_bar.pushMessage( self.tr('Repositories must have unique names!'), Qgis.Warning, 5) return # Redundant if (new_name in self.repository_manager.directories) and (new_name != repo_name): new_name += '(2)' new_auth_cfg = dlg.line_edit_auth_id.text() # Show progress dialog self.show_progress_dialog("Fetching repository's metadata") # Edit repository try: status, editerror = self.repository_manager.edit_directory( repo_name, new_name, old_url, new_url, new_auth_cfg) if status: self.message_bar.pushMessage( self.tr('Repository is successfully updated'), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr('Unable to edit repository: %s') % editerror, Qgis.Warning, 5) except Exception as e: self.message_bar.pushMessage(self.tr('%s') % e, Qgis.Warning, 5) finally: self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() # Deactivate the edit and delete buttons self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def delete_repository(self): """Delete a repository in the tree widget.""" selected_item = self.tree_repositories.currentItem() if selected_item: repo_name = selected_item.text(0) if not repo_name: return # Check if it is among the offical repositories repo_url = self.repository_manager.directories[repo_name]['url'] if repo_url in self.repository_manager._online_directories.values(): self.message_bar.pushMessage( self.tr('You can not remove official repositories!'), Qgis.Warning, 5) return warning = self.tr('Are you sure you want to remove the following ' 'repository?') + '\n' + repo_name if QMessageBox.warning(self, self.tr('QGIS Resource Sharing'), warning, QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: return # Remove repository installed_collections = \ self.collection_manager.get_installed_collections(repo_url) if installed_collections: message = ('You have installed collections from this ' 'repository. Please uninstall them first!') self.message_bar.pushMessage(message, Qgis.Warning, 5) else: self.repository_manager.remove_directory(repo_name) # Reload data and widget self.reload_data_and_widget() # Deactivate the edit and delete buttons self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def reload_repositories(self): """Slot for when user clicks reload repositories button.""" # Show progress dialog self.show_progress_dialog('Reloading all repositories') for repo_name in self.repository_manager.directories: directory = self.repository_manager.directories[repo_name] url = directory['url'] auth_cfg = directory['auth_cfg'] try: status, reloaderror = self.repository_manager.reload_directory( repo_name, url, auth_cfg) if status: self.message_bar.pushMessage( self.tr('Repository %s is successfully reloaded') % repo_name, Qgis.Info, 5) else: self.message_bar.pushMessage( self.tr('Unable to reload %s: %s') % (repo_name, reloaderror), Qgis.Warning, 5) except Exception as e: self.message_bar.pushMessage( self.tr('%s') % e, Qgis.Warning, 5) self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() def install_collection(self): """Slot for when the user clicks the install/reinstall button.""" # Save the current index to enable selection after installation self.current_index = self.list_view_collections.currentIndex() self.show_progress_dialog('Starting installation...') self.progress_dialog.canceled.connect(self.install_canceled) self.installer_thread = QThread() self.installer_worker = CollectionInstaller( self.collection_manager, self._selected_collection_id) self.installer_worker.moveToThread(self.installer_thread) self.installer_worker.finished.connect(self.install_finished) self.installer_worker.aborted.connect(self.install_aborted) self.installer_worker.progress.connect(self.install_progress) self.installer_thread.started.connect(self.installer_worker.run) self.installer_thread.start() def install_finished(self): # Process the result self.progress_dialog.hide() installStatus = self.installer_worker.install_status if not installStatus: message = self.installer_worker.error_message # Clean up the worker and thread self.installer_worker.deleteLater() self.installer_thread.quit() self.installer_thread.wait() self.installer_thread.deleteLater() if installStatus: self.reload_collections_model() # Report what has been installed message = '<b>%s</b> was successfully installed, containing:\n<ul>' % ( config.COLLECTIONS[self._selected_collection_id]['name']) number = 0 if 'style' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['style'] message = message + '\n<li> ' + str( number) + ' Layer style (QML) file' if number > 1: message = message + 's' if 'symbol' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['symbol'] message = message + '\n<li> ' + str( number) + ' XML symbol file' if number > 1: message = message + 's' if 'svg' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['svg'] message = message + '\n<li> ' + str(number) + ' SVG file' if number > 1: message = message + 's' if 'models' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['models'] message = message + '\n<li> ' + str(number) + ' model' if number > 1: message = message + 's' if 'expressions' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['expressions'] message = message + '\n<li> ' + str( number) + ' expression file' if number > 1: message = message + 's' if 'processing' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['processing'] message = message + '\n<li> ' + str( number) + ' processing script' if number > 1: message = message + 's' if 'rscripts' in config.COLLECTIONS[ self._selected_collection_id].keys(): number = config.COLLECTIONS[ self._selected_collection_id]['rscripts'] message = message + '\n<li> ' + str(number) + ' R script' if number > 1: message = message + 's' message = message + '\n</ul>' QMessageBox.information(self, 'Resource Sharing', message) self.populate_repositories_widget() # Set the selection oldRow = self.current_index.row() newIndex = self.collections_model.createIndex(oldRow, 0) selection_model = self.list_view_collections.selectionModel() selection_model.setCurrentIndex(newIndex, selection_model.ClearAndSelect) selection_model.select(newIndex, selection_model.ClearAndSelect) # Update the buttons self.button_install.setEnabled(True) self.button_install.setText('Reinstall') self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) self.show_collection_metadata(self._selected_collection_id) def install_canceled(self): self.progress_dialog.hide() self.show_progress_dialog('Cancelling installation...') self.installer_worker.abort() def install_aborted(self): if self.installer_thread.isRunning(): self.installer_thread.quit() self.installer_thread.finished.connect(self.progress_dialog.hide) def install_progress(self, text): self.progress_dialog.setLabelText(text) def uninstall_collection(self): """Slot called when user clicks the uninstall button.""" # get the QModelIndex for the item to be uninstalled uninstall_index = self.list_view_collections.currentIndex() coll_id = self._selected_collection_id try: self.collection_manager.uninstall(coll_id) except Exception as e: LOGGER.error('Could not uninstall collection ' + config.COLLECTIONS[coll_id]['name'] + ':\n' + str(e)) else: QMessageBox.information( self, 'Resource Sharing', 'The collection was successfully uninstalled!') self.reload_collections_model() # Fix the GUI currentMenuRow = self.menu_list_widget.currentRow() self.set_current_tab(currentMenuRow) self.populate_repositories_widget() rowCount = self.collection_proxy.rowCount() if rowCount > 0: # Set the current (and selected) row in the listview newRow = uninstall_index.row() # Check if this was the last element rowCount = self.collection_proxy.rowCount() if newRow == rowCount: newRow = newRow - 1 # Select the new current element newIndex = self.collections_model.createIndex(newRow, 0) selection_model = self.list_view_collections.selectionModel() selection_model.setCurrentIndex(newIndex, selection_model.ClearAndSelect) # Get the id of the current collection proxyModel = self.list_view_collections.model() proxyIndex = proxyModel.index(newRow, 0) current_coll_id = proxyIndex.data(COLLECTION_ID_ROLE) self._selected_collection_id = current_coll_id # Update buttons status = config.COLLECTIONS[current_coll_id]['status'] if status == COLLECTION_INSTALLED_STATUS: self.button_install.setEnabled(True) self.button_install.setText('Reinstall') self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) else: self.button_install.setEnabled(True) self.button_install.setText('Install') self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Update the web_view_details frame self.show_collection_metadata(current_coll_id) else: self.button_install.setEnabled(False) self.button_install.setText('Install') self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) def open_collection(self): """Slot for when user clicks 'Open' button.""" collection_path = local_collection_path(self._selected_collection_id) directory_url = QUrl.fromLocalFile(str(collection_path)) QDesktopServices.openUrl(directory_url) def reload_data_and_widget(self): """Reload repositories and collections and update widgets related.""" self.reload_repositories_widget() self.reload_collections_model() def reload_repositories_widget(self): """Refresh tree repositories using new repositories data.""" self.repository_manager.load_directories() self.populate_repositories_widget() def populate_repositories_widget(self): """Populate the current dictionary repositories to the tree widget.""" # Clear the current tree widget self.tree_repositories.clear() installed_collections = \ self.collection_manager.get_installed_collections() # Export the updated ones from the repository manager for repo_name in self.repository_manager.directories: url = self.repository_manager.directories[repo_name]['url'] item = QTreeWidgetItem(self.tree_repositories, REPOSITORY_ITEM) item.setText(0, repo_name) item.setText(1, url) for coll_id in config.COLLECTIONS: if ('repository_name' in config.COLLECTIONS[coll_id].keys() and config.COLLECTIONS[coll_id]['repository_name'] == repo_name): coll_name = config.COLLECTIONS[coll_id]['name'] coll_tags = config.COLLECTIONS[coll_id]['tags'] collectionItem = QTreeWidgetItem(item, COLLECTION_ITEM) collitemtext = coll_name if installed_collections and coll_id in installed_collections.keys( ): collitemtext = coll_name + ' (installed)' collectionFont = QFont() collectionFont.setWeight(60) collectionItem.setFont(0, collectionFont) collectionItem.setText(0, collitemtext) collectionItem.setText(1, coll_tags) self.tree_repositories.resizeColumnToContents(0) self.tree_repositories.resizeColumnToContents(1) self.tree_repositories.sortItems(1, Qt.AscendingOrder) def reload_collections_model(self): """Reload the collections model with the current collections.""" self.collections_model.clear() installed_collections = \ self.collection_manager.get_installed_collections() for id in config.COLLECTIONS: collection_name = config.COLLECTIONS[id]['name'] collection_author = config.COLLECTIONS[id]['author'] collection_tags = config.COLLECTIONS[id]['tags'] collection_description = config.COLLECTIONS[id]['description'] collection_status = config.COLLECTIONS[id]['status'] repository_name = '' if 'repository_name' in config.COLLECTIONS[id].keys(): repository_name = config.COLLECTIONS[id]['repository_name'] item = QStandardItem(collection_name + ' (' + repository_name + ')') item.setEditable(False) item.setData(id, COLLECTION_ID_ROLE) item.setData(collection_name, COLLECTION_NAME_ROLE) item.setData(collection_description, COLLECTION_DESCRIPTION_ROLE) item.setData(collection_author, COLLECTION_AUTHOR_ROLE) item.setData(collection_tags, COLLECTION_TAGS_ROLE) item.setData(collection_status, COLLECTION_STATUS_ROLE) # Make installed collections stand out if installed_collections and id in installed_collections.keys(): collectionFont = QFont() collectionFont.setWeight(60) item.setFont(collectionFont) self.collections_model.appendRow(item) self.collections_model.sort(0, Qt.AscendingOrder) def on_tree_repositories_itemSelectionChanged(self): """Slot for the itemSelectionChanged signal of tree_repositories.""" selected_item = self.tree_repositories.currentItem() if selected_item and selected_item.type() == REPOSITORY_ITEM: if selected_item: repo_name = selected_item.text(0) if not repo_name: return if not repo_name in self.repository_manager.directories.keys(): return repo_url = self.repository_manager.directories[repo_name]['url'] # Disable the edit and delete buttons for "official" repositories if repo_url in self.repository_manager._online_directories.values( ): self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) else: # Activate the edit and delete buttons self.button_edit.setEnabled(True) self.button_delete.setEnabled(True) elif selected_item and selected_item.type() == COLLECTION_ITEM: self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) else: self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def on_list_view_collections_clicked(self, index): """Slot for when the list_view_collections is clicked.""" real_index = self.collection_proxy.mapToSource(index) if real_index.row() != -1: collection_item = self.collections_model.itemFromIndex(real_index) collection_id = collection_item.data(COLLECTION_ID_ROLE) self._selected_collection_id = collection_id # Enable / disable buttons status = config.COLLECTIONS[self._selected_collection_id]['status'] is_installed = status == COLLECTION_INSTALLED_STATUS if is_installed: self.button_install.setEnabled(True) self.button_install.setText('Reinstall') self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) else: self.button_install.setEnabled(True) self.button_install.setText('Install') self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Show metadata self.show_collection_metadata(collection_id) @pyqtSlot(str) def filter_collections(self, text): search = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp) self.collection_proxy.setFilterRegExp(search) def show_collection_metadata(self, id): """Show the collection metadata given the ID.""" html = self.collection_manager.get_html(id) self.web_view_details.setHtml(html) def reject(self): """Slot when the dialog is closed.""" # Serialize collections to settings self.repository_manager.serialize_repositories() self.done(0) def open_help(self): """Open help.""" doc_url = QUrl('http://qgis-contribution.github.io/' + 'QGIS-ResourceSharing/') QDesktopServices.openUrl(doc_url) def show_progress_dialog(self, text): """Show infinite progress dialog with given text. :param text: Text as the label of the progress dialog :type text: str """ if self.progress_dialog is None: self.progress_dialog = QProgressDialog(self) self.progress_dialog.setWindowModality(Qt.WindowModal) self.progress_dialog.setAutoClose(False) title = self.tr('Resource Sharing') self.progress_dialog.setWindowTitle(title) # Just use an infinite progress bar here self.progress_dialog.setMaximum(0) self.progress_dialog.setMinimum(0) self.progress_dialog.setValue(0) self.progress_dialog.setLabelText(text) self.progress_dialog.show()
class StatistDialog(BASE, WIDGET): def __init__(self, parent=None): super(StatistDialog, self).__init__(parent) self.setupUi(self) self.thread = QThread() self.calculator = StatisticsCalculator() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.calculator.moveToThread(self.thread) self.calculator.calculated.connect(self.thread.quit) self.calculator.calculated.connect(self.processFinished) self.thread.started.connect(self.calculator.calculate) self.cmbLayer.setFilters(QgsMapLayerProxyModel.VectorLayer) self.cmbField.setLayer(self.cmbLayer.currentLayer()) self.cmbLayer.layerChanged.connect(self.resetGui) self.cmbLayer.layerChanged.connect(self.cmbField.setLayer) self._resetPlot() def resetGui(self, layer): self._resetPlot() self.tblStatistics.setRowCount(0) if layer.selectedFeatureCount() != 0: self.chkSelectedOnly.setChecked(True) else: self.chkSelectedOnly.setChecked(False) def accept(self): self.tblStatistics.setRowCount(0) layer = self.cmbLayer.currentLayer() if self.chkSelectedOnly.isChecked() and layer.selectedFeatureCount() == 0: QgsMessageBar.pushWarning(self.tr("No selection"), self.tr("There is no selection in the input layer. Uncheck " "corresponding option or select some features before " "running analysis")) return self.calculator.setLayer(layer) self.calculator.setField(self.cmbField.currentField()) self.calculator.setSelectedOnly(self.chkSelectedOnly.isChecked()) self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) self.thread.start() def reject(self): QDialog.reject(self) def processFinished(self): rowCount = len(self.calculator.data) self.tblStatistics.setRowCount(rowCount) for i in range(rowCount): tmp = self.calculator.data[i].split(":") item = QTableWidgetItem(tmp[0]) self.tblStatistics.setItem(i, 0, item) item = QTableWidgetItem(tmp[1]) self.tblStatistics.setItem(i, 1, item) self.refreshPlot() self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def refreshPlot(self): if len(self.calculator.values) == 0: return # use correct axis formatter field = self.cmbLayer.currentLayer().fields().field(self.cmbField.currentField()) if field.type() == QVariant.Date: values = [datetime(d.year, d.month, d.day) if d else None for d in self.calculator.values] elif field.type() == QVariant.DateTime: values = self.calculator.values elif field.type() == QVariant.Time: values = [datetime.strptime(t.isoformat(), "%H:%M:%S") if t else None for t in self.calculator.values] else: values = self.calculator.values self.mplWidget.axes.hist(values, "auto", alpha=0.5, histtype="bar", color="#006BA4") self.mplWidget.setXAxisCaption(self.cmbField.currentText()) self.mplWidget.setYAxisCaption(self.tr("Count")) self.mplWidget.alignLabels() self.mplWidget.canvas.draw() def keyPressEvent(self, event): if event.modifiers() in [Qt.ControlModifier, Qt.MetaModifier] and event.key() == Qt.Key_C: clipboard = QApplication.clipboard() clipboard.setText("\n".join(self.calculator.data)) else: QDialog.keyPressEvent(self, event) def _resetPlot(self): self.mplWidget.clear() self.mplWidget.setTitle(self.tr("Frequency distribution"))
class Photo2ShapeDialog(BASE, WIDGET): def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QgsSettings("alexbruy", "photo2shape") self.fwPhotosPath.setStorageMode(QgsFileWidget.GetDirectory) self.fwPhotosPath.setDialogTitle(self.tr("Select directory")) self.fwPhotosPath.setDefaultRoot(self.settings.value("lastPhotosDirectory", os.path.expanduser("~"), str)) self.fwPhotosPath.fileChanged.connect(self.updateLastPhotosPath) self.fwOutputShape.setStorageMode(QgsFileWidget.SaveFile) self.fwOutputShape.setConfirmOverwrite(True) self.fwOutputShape.setDialogTitle(self.tr("Select file")) self.fwOutputShape.setDefaultRoot(self.settings.value("lastShapeDirectory", QgsProject.instance().homePath(), str)) self.fwOutputShape.setFilter(self.tr("ESRI Shapefile (*.shp *.SHP)")) self.fwOutputShape.fileChanged.connect(self.updateLastShapePath) self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value("encoding", "utf-8", str) self.chkRecurse.setChecked(self.settings.value("recurse", True, bool)) self.chkAppend.setChecked(self.settings.value("append", True, bool)) self.chkLoadLayer.setChecked(self.settings.value("loadLayer", True, bool)) def closeEvent(self, event): self._saveSettings() QDialog.closeEvent(self, event) def updateLastPhotosPath(self, dirPath): self.fwPhotosPath.setDefaultRoot(dirPath) self.settings.setValue("lastPhotosDirectory", os.path.dirname(dirPath)) def updateLastShapePath(self, shapePath): self.fwOutputShape.setDefaultRoot(shapePath) self.settings.setValue("lastShapeDirectory", os.path.dirname(shapePath)) def reject(self): self._saveSettings() QDialog.reject(self) def accept(self): self._saveSettings() dirName = self.fwPhotosPath.filePath() if dirName == '': self.iface.messageBar().pushWarning( self.tr("Path is not set"), self.tr("Path to photos is not set. Please specify directory " "with photos and try again.")) return fileName = self.fwOutputShape.filePath() if fileName == "": self.iface.messageBar().pushWarning( self.tr("Output file is not set"), self.tr("Output file name is missing. Please specify correct " "output file and try again.")) return if self.chkAppend.isChecked() and not os.path.isfile(fileName): self.iface.messageBar().pushWarning( self.tr("Appending is not possible"), self.tr("Output file is a new file and can not be used " "in 'append' mode. Either specify existing file " "or uncheck corresponding checkbox.")) return self.importer.setPhotosDirectory(dirName) self.importer.setOutputPath(fileName) self.importer.setEncoding(self.encoding) self.importer.setRecurseDirs(self.chkRecurse.isChecked()) self.importer.setAppendFile(self.chkAppend.isChecked()) self.thread.start() self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) def updateProgress(self, value): self.progressBar.setValue(value) def logMessage(self, message, level=QgsMessageLog.INFO): QgsMessageLog.logMessage(message, "Photo2Shape", level) def importCanceled(self, message): self.iface.messageBar().pushWarning(self.tr("Import error"), message) self._restoreGui() def importCompleted(self): self.iface.messageBar().pushSuccess( self.tr("Import completed"), self.tr("Photos imported sucessfully.")) if self.chkLoadLayer.isChecked(): self._loadLayer() self._restoreGui() def _loadLayer(self): fName = self.fwOutputShape.filePath() layer = QgsVectorLayer(fName, os.path.basename(fName), "ogr") if layer.isValid(): layer.loadNamedStyle( os.path.join(pluginPath, "resources", "photos.qml")) QgsProject.instance().addMapLayer(layer) else: self.iface.messageBar().pushWarning( self.tr("No output"), self.tr("Can not load output file.")) def _restoreGui(self): self.progressBar.setValue(0) self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def _saveSettings(self): self.settings.setValue("recurse", self.chkRecurse.isChecked()) self.settings.setValue("append", self.chkAppend.isChecked()) self.settings.setValue("loadLayer", self.chkLoadLayer.isChecked())
class ProjectUpdater(QObject): """ Object to handle reporting when new versions of projects are found on the update server. Emits foundProjects when a new projects are found """ foundProjects = pyqtSignal(list, list) projectUpdateStatus = pyqtSignal(str, str) projectInstalled = pyqtSignal(str) def __init__(self, server=None, projects_base=''): super(ProjectUpdater, self).__init__() self.updatethread = None self.server = server self.net = QgsNetworkAccessManager.instance() self.projects_base = projects_base self.create_worker() def quit(self): self.updatethread.quit() def create_worker(self): self.updatethread = QThread() self.worker = UpdateWorker(self.projects_base) self.worker.moveToThread(self.updatethread) self.worker.projectUpdateStatus.connect(self.projectUpdateStatus) self.worker.projectInstalled.connect(self.projectInstalled) self.updatethread.started.connect(self.worker.run) def configurl(self): url = urljoin(add_slash(self.server), "projects/roam.txt") print("URL", url) return url def check_updates(self, server, installedprojects): if not server: return self.server = server req = QNetworkRequest(QUrl(self.configurl())) reply = self.net.get(req) reply.finished.connect( functools.partial(self.list_versions, reply, installedprojects)) def update_server(self, newserverurl, installedprojects): self.check_updates(newserverurl, installedprojects) def list_versions(self, reply, installedprojects): if reply.error() == QNetworkReply.NoError: content = reply.readAll().data() serverversions = parse_serverprojects(content) updateable = list( updateable_projects(installedprojects, serverversions)) new = list(new_projects(installedprojects, serverversions)) print(updateable) print(new) if updateable or new: self.foundProjects.emit(updateable, new) else: msg = "Error in network request for projects: {}".format( reply.errorString()) roam.utils.warning(msg) def update_project(self, projectinfo): self.projectUpdateStatus.emit(projectinfo['name'], "Pending") forupdate.put( (projectinfo, projectinfo['version'], self.server, False)) self.updatethread.start() def install_project(self, projectinfo): self.projectUpdateStatus.emit(projectinfo['name'], "Pending") is_new = True forupdate.put( (projectinfo, projectinfo['version'], self.server, is_new)) self.updatethread.start()
class NNJoinDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" self.iface = iface self.plugin_dir = dirname(__file__) # Some translated text (to enable reuse) self.NNJOIN = self.tr('NNJoin') self.CANCEL = self.tr('Cancel') self.CLOSE = self.tr('Close') self.HELP = self.tr('Help') self.OK = self.tr('OK') super(NNJoinDialog, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html#\ # widgets-and-dialogs-with-auto-connect self.setupUi(self) # Modify ui components okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) self.cancelButton = self.button_box.button(QDialogButtonBox.Cancel) self.cancelButton.setText(self.CANCEL) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) self.approximate_input_geom_cb.setCheckState(Qt.Unchecked) self.approximate_input_geom_cb.setVisible(False) stytxt = "QCheckBox:checked {color: red; background-color: white}" self.approximate_input_geom_cb.setStyleSheet(stytxt) self.use_indexapprox_cb.setCheckState(Qt.Unchecked) self.use_indexapprox_cb.setVisible(False) self.use_indexapprox_cb.setStyleSheet(stytxt) self.use_index_nonpoint_cb.setCheckState(Qt.Unchecked) self.use_index_nonpoint_cb.setVisible(False) self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) # Help button helpButton = self.helpButton helpButton.setText(self.HELP) # Connect signals okButton.clicked.connect(self.startWorker) # self.cancelButton.clicked.connect(self.killWorker) closeButton.clicked.connect(self.reject) helpButton.clicked.connect(self.help) self.approximate_input_geom_cb.stateChanged['int'].connect( self.useindexchanged) self.use_indexapprox_cb.stateChanged['int'].connect( self.useindexchanged) self.use_index_nonpoint_cb.stateChanged['int'].connect( self.useindexchanged) inpIndexCh = self.inputVectorLayer.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) joinIndexCh = self.joinVectorLayer.currentIndexChanged['QString'] # joinIndexCh.connect(self.layerchanged) joinIndexCh.connect(self.joinlayerchanged) # self.distancefieldname.editingFinished.connect(self.fieldchanged) self.distancefieldname.textChanged.connect(self.distfieldchanged) self.joinPrefix.editingFinished.connect(self.fieldchanged) theRegistry = QgsProject.instance() theRegistry.layersAdded.connect(self.layerlistchanged) theRegistry.layersRemoved.connect(self.layerlistchanged) # Disconnect the cancel button to avoid exiting. self.button_box.rejected.disconnect(self.reject) # Set instance variables self.mem_layer = None self.worker = None self.inputlayerid = None self.joinlayerid = None self.layerlistchanging = False def startWorker(self): """Initialises and starts the worker thread.""" try: layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) if joinlayer is None: self.showError(self.tr('No join layer defined')) return if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') outputlayername = self.outputDataset.text() approximateinputgeom = self.approximate_input_geom_cb.isChecked() joinprefix = self.joinPrefix.text() # useindex = True useindex = self.use_index_nonpoint_cb.isChecked() useindexapproximation = self.use_indexapprox_cb.isChecked() distancefieldname = self.distancefieldname.text() selectedinputonly = self.inputSelected.isChecked() selectedjoinonly = self.joinSelected.isChecked() excludecontaining = self.exclude_containing_poly_cb.isChecked() # create a new worker instance self.worker = Worker(inputlayer, joinlayer, outputlayername, joinprefix, distancefieldname, approximateinputgeom, useindexapproximation, useindex, selectedinputonly, selectedjoinonly, excludecontaining) # configure the QgsMessageBar msgBar = self.iface.messageBar().createMessage( self.tr('Joining'), '') self.aprogressBar = QProgressBar() self.aprogressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) acancelButton = QPushButton() acancelButton.setText(self.CANCEL) # acancelButton.clicked.connect(self.killWorker) msgBar.layout().addWidget(self.aprogressBar) msgBar.layout().addWidget(acancelButton) # Has to be popped after the thread has finished (in # workerFinished). self.iface.messageBar().pushWidget(msgBar, Qgis.Info) # self.iface.messageBar().INFO) self.messageBar = msgBar # start the worker in a new thread self.mythread = QThread(self) # QT requires the "self" self.worker.status.connect(self.workerInfo) self.worker.progress.connect(self.progressBar.setValue) self.worker.progress.connect(self.aprogressBar.setValue) self.worker.finished.connect(self.workerFinished) self.worker.error.connect(self.workerError) # Must come before movetothread: self.cancelButton.clicked.connect(self.worker.kill) acancelButton.clicked.connect(self.worker.kill) self.worker.finished.connect(self.worker.deleteLater) self.worker.finished.connect(self.mythread.quit) # self.worker.error.connect(self.worker.deleteLater) # self.worker.error.connect(self.mythread.quit) # Must come before thread.started.connect!: self.worker.moveToThread(self.mythread) self.mythread.started.connect(self.worker.run) self.mythread.finished.connect(self.mythread.deleteLater) self.mythread.start() # self.thread = thread # self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) if layerId == joinlayerId: self.showInfo("The join layer is the same as the" " input layer - doing a self join!") except: import traceback self.showError("Error starting worker: " + traceback.format_exc()) else: pass # End of startworker def workerFinished(self, ok, ret): """Handles the output from the worker and cleans up after the worker has finished.""" # remove widget from message bar (pop) self.iface.messageBar().popWidget(self.messageBar) if ok and ret is not None: # report the result mem_layer = ret QgsMessageLog.logMessage(self.tr('NNJoin finished'), self.NNJOIN, Qgis.Info) mem_layer.dataProvider().updateExtents() mem_layer.commitChanges() self.layerlistchanging = True QgsProject.instance().addMapLayer(mem_layer) self.layerlistchanging = False else: # notify the user that something went wrong if not ok: self.showError(self.tr('Aborted') + '!') else: self.showError(self.tr('No layer created') + '!') self.progressBar.setValue(0.0) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.button_box.button(QDialogButtonBox.Close).setEnabled(True) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) # End of workerFinished def workerError(self, exception_string): """Report an error from the worker.""" self.showError(exception_string) def workerInfo(self, message_string): """Report an info message from the worker.""" QgsMessageLog.logMessage( self.tr('Worker') + ': ' + message_string, self.NNJOIN, Qgis.Info) def fieldchanged(self, number=0): # If the layer list is being updated, don't do anything if self.layerlistchanging: return self.updateui() # End of fieldchanged def distfieldchanged(self, number=0): # If the layer list is being updated, don't do anything # if self.layerlistchanging: # return # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Enable the OK button (if layers are OK) if inputlayer is not None and joinlayer is not None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) if inputlayer is not None: # Set the default background (white) for the distance field name self.distancefieldname.setStyleSheet("background:#fff;") # Check if the distance field name already is used inputfields = inputlayer.fields().toList() for infield in inputfields: if infield.name() == self.distancefieldname.text(): self.distancefieldname.setStyleSheet("background:#f00;") self.showInfo( "Distance field name conflict in input layer") if self.button_box.button(QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) if joinlayer is not None: joinfields = joinlayer.fields().toList() for joinfield in joinfields: if (self.joinPrefix.text() + joinfield.name() == self.distancefieldname.text()): self.distancefieldname.setStyleSheet( "background:#f00;") self.showInfo( "Distance field name conflict in join layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) # self.updateui() # End of distfieldchanged def joinlayerchanged(self, number=0): # If the layer list is being updated, don't do anything if self.layerlistchanging: return # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) self.joinlayerid = joinlayerId joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Geographic? - give a warning! if joinlayer is not None and joinlayer.crs().isGeographic(): self.showWarning('Geographic CRS used for the join layer -' ' distances will be in decimal degrees!') self.layerchanged() # End of joinlayerchanged def layerchanged(self, number=0): """Do the necessary updates after a layer selection has been changed.""" # If the layer list is being updated, don't do anything if self.layerlistchanging: return # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) self.inputlayerid = layerId inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) self.joinlayerid = joinlayerId joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Update the input layer UI label with input geometry # type information if inputlayer is not None: inputwkbtype = inputlayer.wkbType() # inputlayerwkbtext = self.getwkbtext(inputwkbtype) inputlayerwkbtext = QgsWkbTypes.displayString(inputwkbtype) self.inputgeometrytypelabel.setText(inputlayerwkbtext) # Update the join layer UI label with join geometry type # information if joinlayer is not None: joinwkbtype = joinlayer.wkbType() #joinlayerwkbtext = self.getwkbtext(joinwkbtype) joinlayerwkbtext = QgsWkbTypes.displayString(joinwkbtype) self.joingeometrytypelabel.setText(joinlayerwkbtext) # Check the coordinate systems # Different CRSs? - give a warning! if (inputlayer is not None and joinlayer is not None and inputlayer.crs() != joinlayer.crs()): self.showWarning( 'Layers have different CRS! - Input CRS authid: ' + str(inputlayer.crs().authid()) + ' - Join CRS authid: ' + str(joinlayer.crs().authid()) + ". The input layer will be transformed.") self.updateui() # end of layerchanged def useindexchanged(self, number=0): self.updateui() def layerlistchanged(self): # When a layer has been added to or removed by the user, # the comboboxes should be updated to include the new # possibilities. self.layerlistchanging = True # Repopulate the input and join layer combo boxes # Save the currently selected input layer inputlayerid = self.inputlayerid layers = QgsProject.instance().mapLayers() layerslist = [] for id in layers.keys(): if layers[id].type() == QgsMapLayer.VectorLayer: if not layers[id].isValid(): QMessageBox.information( None, self.tr('Information'), 'Layer ' + layers[id].name() + ' is not valid') if layers[id].wkbType() != QgsWkbTypes.NoGeometry: layerslist.append((layers[id].name(), id)) # Add the layers to the input layers combobox self.inputVectorLayer.clear() for layerdescription in layerslist: self.inputVectorLayer.addItem(layerdescription[0], layerdescription[1]) # Set the previous selection for the input layer for i in range(self.inputVectorLayer.count()): if self.inputVectorLayer.itemData(i) == inputlayerid: self.inputVectorLayer.setCurrentIndex(i) # Save the currently selected join layer joinlayerid = self.joinlayerid # Add the layers to the join layers combobox self.joinVectorLayer.clear() for layerdescription in layerslist: self.joinVectorLayer.addItem(layerdescription[0], layerdescription[1]) # Set the previous selection for the join layer for i in range(self.joinVectorLayer.count()): if self.joinVectorLayer.itemData(i) == joinlayerid: self.joinVectorLayer.setCurrentIndex(i) self.layerlistchanging = False self.updateui() # End of layerlistchanged def updateui(self): """Do the necessary updates after a layer selection has been changed.""" # if self.layerlistchanged: # return # Update the output dataset name self.outputDataset.setText(self.inputVectorLayer.currentText() + '_' + self.joinVectorLayer.currentText()) # Retrieve the input layer layerindex = self.inputVectorLayer.currentIndex() layerId = self.inputVectorLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) # Retrieve the join layer joinindex = self.joinVectorLayer.currentIndex() joinlayerId = self.joinVectorLayer.itemData(joinindex) joinlayer = QgsProject.instance().mapLayer(joinlayerId) # Enable the OK button (if layers are OK) if inputlayer is not None and joinlayer is not None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) # Check the geometry type of the input layer and set # user interface options accordingly if inputlayer is not None: wkbType = inputlayer.wkbType() geomType = inputlayer.geometryType() # not used yet joinwkbType = QgsWkbTypes.Unknown joingeomType = QgsWkbTypes.UnknownGeometry # not used yet if joinlayer is not None: joinwkbType = joinlayer.wkbType() joingeomType = joinlayer.geometryType() # If the input layer is not a point layer, allow choosing # approximate geometry (centroid) if wkbType == QgsWkbTypes.Point or wkbType == QgsWkbTypes.Point25D: # Input layer is a simple point layer and can not # be approximated self.approximate_input_geom_cb.blockSignals(True) self.approximate_input_geom_cb.setCheckState(Qt.Unchecked) self.approximate_input_geom_cb.setVisible(False) self.approximate_input_geom_cb.blockSignals(False) else: # Input layer is not a point layer, so approximation # is possible self.approximate_input_geom_cb.blockSignals(True) self.approximate_input_geom_cb.setVisible(True) self.approximate_input_geom_cb.blockSignals(False) # Update the use index checkbox if ((wkbType == QgsWkbTypes.LineString or wkbType == QgsWkbTypes.LineString25D or wkbType == QgsWkbTypes.Polygon or wkbType == QgsWkbTypes.Polygon25D) and not self.approximate_input_geom_cb.isChecked()): # The input layer is a line or polygong layer that # is not approximated, so the user is allowed to # choose not to use the spatial index (not very useful!) if not self.use_index_nonpoint_cb.isVisible(): self.use_index_nonpoint_cb.blockSignals(True) self.use_index_nonpoint_cb.setCheckState(Qt.Checked) self.use_index_nonpoint_cb.setVisible(True) self.use_index_nonpoint_cb.blockSignals(False) else: # The input layer is either a point approximation # or it is a point layer (or some kind of # multigeometry!), anyway we won't allow the user to # choose not to use a spatial index self.use_index_nonpoint_cb.blockSignals(True) self.use_index_nonpoint_cb.setCheckState(Qt.Unchecked) self.use_index_nonpoint_cb.setVisible(False) self.use_index_nonpoint_cb.blockSignals(False) # This does not work!!???? # Update the use index approximation checkbox: if (((wkbType == QgsWkbTypes.Point) or (wkbType == QgsWkbTypes.Point25D) or self.approximate_input_geom_cb.isChecked()) and not (joinwkbType == QgsWkbTypes.Point or joinwkbType == QgsWkbTypes.Point25D)): # For non-point join layers and point input layers, # the user is allowed to choose an approximation (the # index geometry) to be used for the join geometry in # the join. self.use_indexapprox_cb.setVisible(True) else: # For point join layers, and non-point, # non-point-approximated input layers, the user is # not allowed to choose an approximation (the index # geometry) to be used for the join geometry in the # join. self.use_indexapprox_cb.blockSignals(True) self.use_indexapprox_cb.setCheckState(Qt.Unchecked) self.use_indexapprox_cb.setVisible(False) self.use_indexapprox_cb.blockSignals(False) # Update the exclude containing polygon checkbox: if ((wkbType == QgsWkbTypes.Point or wkbType == QgsWkbTypes.Point25D or self.approximate_input_geom_cb.isChecked()) and (joinwkbType == QgsWkbTypes.Polygon or joinwkbType == QgsWkbTypes.Polygon25D) and (not self.use_indexapprox_cb.isChecked())): # For polygon join layers and point input layers, # the user is allowed to choose to exclude the # containing polygon in the join. self.exclude_containing_poly_cb.blockSignals(True) self.exclude_containing_poly_cb.setVisible(True) self.exclude_containing_poly_cb.blockSignals(False) else: self.exclude_containing_poly_cb.blockSignals(True) self.exclude_containing_poly_cb.setCheckState(Qt.Unchecked) self.exclude_containing_poly_cb.setVisible(False) self.exclude_containing_poly_cb.blockSignals(False) # Set the default background (white) for the distance field name self.distancefieldname.setStyleSheet("background:#fff;") # Check if the distance field name already is used inputfields = inputlayer.fields().toList() for infield in inputfields: if infield.name() == self.distancefieldname.text(): self.distancefieldname.setStyleSheet("background:#f00;") self.showInfo( "Distance field name conflict in input layer") if self.button_box.button(QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) break if joinlayer is not None: joinfields = joinlayer.fields().toList() for joinfield in joinfields: if (self.joinPrefix.text() + joinfield.name() == self.distancefieldname.text()): self.distancefieldname.setStyleSheet( "background:#f00;") self.showInfo( "Distance field name conflict in join layer") if self.button_box.button( QDialogButtonBox.Ok).isEnabled(): self.button_box.button( QDialogButtonBox.Ok).setEnabled(False) break else: # No input layer defined, so options are disabled self.approximate_input_geom_cb.setVisible(False) self.use_indexapprox_cb.setVisible(False) self.use_index_nonpoint_cb.setVisible(False) # End of updateui def getwkbtext(self, number): if number == QgsWkbTypes.Unknown: return "Unknown" elif number == QgsWkbTypes.Point: return "Point" elif number == QgsWkbTypes.PointZ: self.showWarning( 'The Z coordinate will be ignored for PointZ layers') return "PointZ" elif number == QgsWkbTypes.LineString: return "LineString" elif number == QgsWkbTypes.Polygon: return "Polygon" elif number == QgsWkbTypes.MultiPoint: return "MultiPoint" elif number == QgsWkbTypes.MultiLineString: return "MultiLineString" elif number == QgsWkbTypes.MultiPolygon: return "MultiPolygon" elif number == QgsWkbTypes.NoGeometry: return "NoGeometry" elif number == QgsWkbTypes.Point25D: return "Point25D" elif number == QgsWkbTypes.LineString25D: return "LineString25D" elif number == QgsWkbTypes.Polygon25D: return "Polygon25D" elif number == QgsWkbTypes.MultiPoint25D: return "MultiPoint25D" elif number == QgsWkbTypes.MultiLineString25D: return "MultiLineString25D" elif number == QgsWkbTypes.MultiPolygon25D: return "MultiPolygon25D" else: self.showError('Unknown or invalid geometry type: ' + str(number)) return "Don't know" # End of getwkbtext def killWorker(self): """Kill the worker thread.""" # if self.worker is not None: # self.showInfo(self.tr('Killing worker')) # self.worker.kill() def showError(self, text): """Show an error.""" self.iface.messageBar().pushMessage(self.tr('Error'), text, level=Qgis.Critical, duration=3) QgsMessageLog.logMessage('Error: ' + text, self.NNJOIN, Qgis.Critical) def showWarning(self, text): """Show a warning.""" self.iface.messageBar().pushMessage(self.tr('Warning'), text, level=Qgis.Warning, duration=2) QgsMessageLog.logMessage('Warning: ' + text, self.NNJOIN, Qgis.Warning) def showInfo(self, text): """Show info.""" self.iface.messageBar().pushMessage(self.tr('Info'), text, level=Qgis.Info, duration=2) QgsMessageLog.logMessage('Info: ' + text, self.NNJOIN, Qgis.Info) def help(self): QDesktopServices.openUrl( QUrl.fromLocalFile(self.plugin_dir + "/help/html/index.html")) # showPluginHelp(None, "help/html/index") def tr(self, message): """Get the translation for a string using Qt translation API. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ return QCoreApplication.translate('NNJoinDialog', message) # Implement the accept method to avoid exiting the dialog when # starting the work def accept(self): """Accept override.""" pass # Implement the reject method to have the possibility to avoid # exiting the dialog when cancelling def reject(self): """Reject override.""" # exit the dialog QDialog.reject(self)
def startWorker(self): # self.showInfo('Ready to start worker') self.degfreedCorr = self.degfreedcorr_cb.isChecked() self.crimestatCorr = self.crimestatcorr_cb.isChecked() # Get the input layer layerindex = self.InputLayer.currentIndex() layerId = self.InputLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return self.featureCount = 0 if self.selectedFeatures_cb.isChecked(): self.featureCount = inputlayer.selectedFeatureCount() if self.featureCount == 0: self.featureCount = inputlayer.featureCount() if self.featureCount < 2: self.showError(self.tr('Not enough features')) # self.scene.clear() return if (self.useWeights_cb.isChecked() and self.inputField.count() == 0): self.showError(self.tr('Missing numerical field')) return fieldindex = self.inputField.currentIndex() fieldname = self.inputField.itemData(fieldindex) # inpfield = inputlayer.fieldNameIndex(fieldindex) # minval = inputlayer.minimumValue(inpfield) if (not self.useWeights_cb.isChecked()): fieldname = None self.result = None self.SDLayer = inputlayer self.method = 1 if self.yuill_rb.isChecked(): self.method = 1 elif self.crimestat_rb.isChecked(): self.method = 2 if self.featureCount < 3 and (self.method == 2 or self.degfreedCorr): self.showError(self.tr('Not enough features')) return # create a new worker instance worker = Worker(inputlayer, self.selectedFeatures_cb.isChecked(), fieldname, self.method) # start the worker in a new thread thread = QThread(self) worker.moveToThread(thread) worker.finished.connect(self.workerFinished) worker.error.connect(self.workerError) worker.status.connect(self.workerInfo) worker.progress.connect(self.progressBar.setValue) # worker.progress.connect(self.aprogressBar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) self.InputLayer.setEnabled(False) self.inputField.setEnabled(False)
class Photo2ShapeDialog(BASE, WIDGET): def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QgsSettings("alexbruy", "photo2shape") self.fwPhotosPath.setStorageMode(QgsFileWidget.GetDirectory) self.fwPhotosPath.setDialogTitle(self.tr("Select directory")) self.fwPhotosPath.setDefaultRoot( self.settings.value("lastPhotosDirectory", os.path.expanduser("~"), str)) self.fwPhotosPath.fileChanged.connect(self.updateLastPhotosPath) self.fwOutputShape.setStorageMode(QgsFileWidget.SaveFile) self.fwOutputShape.setConfirmOverwrite(True) self.fwOutputShape.setDialogTitle(self.tr("Select file")) self.fwOutputShape.setDefaultRoot( self.settings.value("lastShapeDirectory", QgsProject.instance().homePath(), str)) self.fwOutputShape.setFilter(self.tr("ESRI Shapefile (*.shp *.SHP)")) self.fwOutputShape.fileChanged.connect(self.updateLastShapePath) self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value("encoding", "utf-8", str) self.chkRecurse.setChecked(self.settings.value("recurse", True, bool)) self.chkAppend.setChecked(self.settings.value("append", True, bool)) self.chkLoadLayer.setChecked( self.settings.value("loadLayer", True, bool)) def closeEvent(self, event): self._saveSettings() QDialog.closeEvent(self, event) def updateLastPhotosPath(self, dirPath): self.fwPhotosPath.setDefaultRoot(dirPath) self.settings.setValue("lastPhotosDirectory", os.path.dirname(dirPath)) def updateLastShapePath(self, shapePath): self.fwOutputShape.setDefaultRoot(shapePath) self.settings.setValue("lastShapeDirectory", os.path.dirname(shapePath)) def reject(self): self._saveSettings() QDialog.reject(self) def accept(self): self._saveSettings() dirName = self.fwPhotosPath.filePath() if dirName == '': self.iface.messageBar().pushWarning( self.tr("Path is not set"), self.tr("Path to photos is not set. Please specify directory " "with photos and try again.")) return fileName = self.fwOutputShape.filePath() if fileName == "": self.iface.messageBar().pushWarning( self.tr("Output file is not set"), self.tr("Output file name is missing. Please specify correct " "output file and try again.")) return if self.chkAppend.isChecked() and not os.path.isfile(fileName): self.iface.messageBar().pushWarning( self.tr("Appending is not possible"), self.tr("Output file is a new file and can not be used " "in 'append' mode. Either specify existing file " "or uncheck corresponding checkbox.")) return self.importer.setPhotosDirectory(dirName) self.importer.setOutputPath(fileName) self.importer.setEncoding(self.encoding) self.importer.setRecurseDirs(self.chkRecurse.isChecked()) self.importer.setAppendFile(self.chkAppend.isChecked()) self.thread.start() self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) def updateProgress(self, value): self.progressBar.setValue(value) def logMessage(self, message, level=QgsMessageLog.INFO): QgsMessageLog.logMessage(message, "Photo2Shape", level) def importCanceled(self, message): self.iface.messageBar().pushWarning(self.tr("Import error"), message) self._restoreGui() def importCompleted(self): self.iface.messageBar().pushSuccess( self.tr("Import completed"), self.tr("Photos imported sucessfully.")) if self.chkLoadLayer.isChecked(): self._loadLayer() self._restoreGui() def _loadLayer(self): fName = self.fwOutputShape.filePath() layer = QgsVectorLayer(fName, os.path.basename(fName), "ogr") if layer.isValid(): layer.loadNamedStyle( os.path.join(pluginPath, "resources", "photos.qml")) QgsProject.instance().addMapLayer(layer) else: self.iface.messageBar().pushWarning( self.tr("No output"), self.tr("Can not load output file.")) def _restoreGui(self): self.progressBar.setValue(0) self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def _saveSettings(self): self.settings.setValue("recurse", self.chkRecurse.isChecked()) self.settings.setValue("append", self.chkAppend.isChecked()) self.settings.setValue("loadLayer", self.chkLoadLayer.isChecked())
class Photo2ShapeDialog(BASE, WIDGET): def __init__(self, iface, parent=None): super(Photo2ShapeDialog, self).__init__(parent) self.setupUi(self) self.iface = iface self.settings = QSettings('alexbruy', 'photo2shape') self.thread = QThread() self.importer = PhotoImporter() self.btnOk = self.buttonBox.button(QDialogButtonBox.Ok) self.btnClose = self.buttonBox.button(QDialogButtonBox.Close) self.btnSelectInput.clicked.connect(self.selectDirectory) self.btnSelectOutput.clicked.connect(self.selectFile) self.importer.moveToThread(self.thread) self.importer.importError.connect(self.thread.quit) self.importer.importError.connect(self.importCanceled) self.importer.importMessage.connect(self.logMessage) self.importer.importFinished.connect(self.thread.quit) self.importer.importFinished.connect(self.importCompleted) self.importer.photoProcessed.connect(self.updateProgress) self.thread.started.connect(self.importer.importPhotos) self.encoding = self.settings.value('encoding', 'System') self.manageGui() def manageGui(self): self.chkRecurse.setChecked(self.settings.value('recurse', True, bool)) self.chkAppend.setChecked(self.settings.value('append', True, bool)) self.chkLoadLayer.setChecked( self.settings.value('loadLayer', True, bool)) def closeEvent(self, event): self._saveSettings() QDialog.closeEvent(self, event) def selectDirectory(self): lastDir = self.settings.value('lastPhotosDir', '.') dirName = QFileDialog.getExistingDirectory( self, self.tr('Select directory'), lastDir) if dirName == '': return self.lePhotosPath.setText(dirName) self.settings.setValue('lastPhotosDir', os.path.dirname(dirName)) def selectFile(self): lastDir = self.settings.value('lastShapeDir', '.') shpFilter = self.tr('ESRI Shapefiles (*.shp *.SHP)') self.encoding = self.settings.value('encoding', 'System') fileDialog = QgsEncodingFileDialog( self, self.tr('Save file'), lastDir, shpFilter, self.encoding) fileDialog.setDefaultSuffix('shp') fileDialog.setFileMode(QFileDialog.AnyFile) fileDialog.setAcceptMode(QFileDialog.AcceptSave) fileDialog.setConfirmOverwrite(True) if fileDialog.exec_(): fileName = fileDialog.selectedFiles()[0] self.encoding = fileDialog.encoding() self.leOutputShape.setText(fileName) self.settings.setValue('lastShapeDir', QFileInfo(fileName).absoluteDir().absolutePath()) self.settings.setValue('encoding', self.encoding) def reject(self): self._saveSettings() QDialog.reject(self) def accept(self): self._saveSettings() dirName = self.lePhotosPath.text() if dirName == '': self.iface.messageBar().pushWarning( self.tr('Path not set'), self.tr('Path to photos is not set. Please specify directory ' 'with photos and try again.')) return fileName = self.leOutputShape.text() if fileName == '': self.iface.messageBar().pushWarning( self.tr('Output file is not set'), self.tr('Output file name is missing. Please specify correct ' 'output file and try again.')) return # make sure output file always has correct suffix if not fileName.lower().endswith('.shp'): fileName += '.shp' self.leOutputShape.setText(fileName) if self.chkAppend.isChecked() and not os.path.isfile(fileName): self.iface.messageBar().pushWarning( self.tr('Appending is not possible'), self.tr('Output file is a new file and can not be used ' 'in "append" mode. Either specify existing file ' 'or uncheck corresponding checkbox.')) return self.importer.setPhotosDirectory(dirName) self.importer.setOutputPath(fileName) self.importer.setEncoding(self.encoding) self.importer.setRecurseDirs(self.chkRecurse.isChecked()) self.importer.setAppendFile(self.chkAppend.isChecked()) self.thread.start() self.btnOk.setEnabled(False) self.btnClose.setEnabled(False) def updateProgress(self, value): self.progressBar.setValue(value) def logMessage(self, message, level=QgsMessageLog.INFO): QgsMessageLog.logMessage(message, 'Photo2Shape', level) def importCanceled(self, message): self.iface.messageBar().pushWarning(self.tr('Import error'), message) self._restoreGui() def importCompleted(self): self.iface.messageBar().pushSuccess( self.tr('Import completed'), self.tr('Shapefile from photos sucessfully created')) if self.chkLoadLayer.isChecked(): self._loadLayer() self._restoreGui() def _loadLayer(self): fName = self.leOutputShape.text() layer = QgsVectorLayer(fName, QFileInfo(fName).baseName(), 'ogr') if layer.isValid(): layer.loadNamedStyle( os.path.join(pluginPath, 'resources', 'photos.qml')) QgsMapLayerRegistry.instance().addMapLayer(layer) else: self.iface.messageBar().pushWarning( self.tr('No output'), self.tr('Cannot load output shapefile')) def _restoreGui(self): self.progressBar.setValue(0) self.btnOk.setEnabled(True) self.btnClose.setEnabled(True) def _saveSettings(self): self.settings.setValue('recurse', self.chkRecurse.isChecked()) self.settings.setValue('append', self.chkAppend.isChecked()) self.settings.setValue('loadLayer', self.chkLoadLayer.isChecked())
class ResourceSharingDialog(QDialog, FORM_CLASS): TAB_ALL = 0 TAB_INSTALLED = 1 TAB_SETTINGS = 2 def __init__(self, parent=None, iface=None): """Constructor. :param parent: Optional widget to use as parent :type parent: QWidget :param iface: An instance of QGisInterface :type iface: QGisInterface """ super(ResourceSharingDialog, self).__init__(parent) self.setupUi(self) self.iface = iface # Reconfigure UI self.setModal(True) self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) self.button_install.setEnabled(False) self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Set up the "main menu" - QListWidgetItem # All collections icon_all = QIcon() icon_all.addFile(str(resources_path("img", "plugin.svg")), QSize(), QIcon.Normal, QIcon.Off) item_all = QListWidgetItem() item_all.setIcon(icon_all) item_all.setText(self.tr("All collections")) # Installed collections icon_installed = QIcon() icon_installed.addFile( str(resources_path("img", "plugin-installed.svg")), QSize(), QIcon.Normal, QIcon.Off, ) item_installed = QListWidgetItem() item_installed.setIcon(icon_installed) item_installed.setText(self.tr("Installed collections")) item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Settings / repositories icon_settings = QIcon() icon_settings.addFile(str(resources_path("img", "settings.svg")), QSize(), QIcon.Normal, QIcon.Off) item_settings = QListWidgetItem() item_settings.setIcon(icon_settings) item_settings.setText(self.tr("Settings")) item_all.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Add the items to the list widget self.menu_list_widget.addItem(item_all) self.menu_list_widget.addItem(item_installed) self.menu_list_widget.addItem(item_settings) # Init the message bar self.message_bar = QgsMessageBar(self) self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.vlayoutRightColumn.insertWidget(0, self.message_bar) # Progress dialog for long running processes self.progress_dialog = None # Init the repository manager dialog self.repository_manager = RepositoryManager() self.collection_manager = CollectionManager() # Collections list view self.collections_model = QStandardItemModel(0, 1) self.collections_model.sort(0, Qt.AscendingOrder) self.collection_proxy = CustomSortFilterProxyModel(self) self.collection_proxy.setSourceModel(self.collections_model) self.list_view_collections.setModel(self.collection_proxy) # Active selected collection self._sel_coll_id = None # Slots self.button_add.clicked.connect(self.add_repository) self.button_edit.clicked.connect(self.edit_repository) self.button_delete.clicked.connect(self.delete_repository) self.button_reload.clicked.connect(self.reload_repositories) self.button_reload_dir.clicked.connect(self.reload_off_res_directory) self.menu_list_widget.currentRowChanged.connect(self.set_current_tab) self.list_view_collections.selectionModel().currentChanged.connect( self.on_list_view_collections_clicked) self.line_edit_filter.textChanged.connect(self.filter_collections) self.button_install.clicked.connect(self.install_collection) self.button_open.clicked.connect(self.open_collection) self.button_uninstall.clicked.connect(self.uninstall_collection) self.button_box.button(QDialogButtonBox.Help).clicked.connect( self.open_help) # Populate the repositories widget and collections list view self.populate_repositories_widget() self.reload_collections_model() def set_current_tab(self, index): """Set stacked widget based on the active tab. :param index: The index of the active widget (in the list widget). :type index: int """ # Clear message bar self.message_bar.clearWidgets() if index == (self.menu_list_widget.count() - 1): # Last menu entry - Settings self.stacked_menu_widget.setCurrentIndex(1) else: # Not settings, must be Collections (all or installed) if index == 1: # Installed collections self.collection_proxy.accepted_status = COLLECTION_INSTALLED_STATUS # Set the web view title = self.tr("Installed Collections") description = self.tr( "On the left you see the list of all the " "installed collections.") else: # All collections (0) self.collection_proxy.accepted_status = COLLECTION_ALL_STATUS # Set the web view title = self.tr("All Collections") description = self.tr( "On the left you see a list of all the collections " "that are available from the registered repositories.<br> " "Installed collections are emphasized (in <b>bold</b>).") context = { "resources_path": str(resources_path()), "title": title, "description": description, } self.web_view_details.setHtml( render_template("tab_description.html", context)) self.stacked_menu_widget.setCurrentIndex(0) def add_repository(self): """Open add repository dialog.""" dlg = ManageRepositoryDialog(self) if not dlg.exec_(): return for repoName, repo in self.repository_manager.directories.items(): if dlg.line_edit_url.text().strip() == repo["url"]: self.message_bar.pushMessage( self.tr( "Unable to add another repository with the same URL!"), Qgis.Warning, 5, ) return if dlg.line_edit_name.text().strip() == repoName: self.message_bar.pushMessage( self.tr("Repositories must have unique names!"), Qgis.Warning, 5) return repo_name = dlg.line_edit_name.text() repo_url = dlg.line_edit_url.text().strip() repo_auth_cfg = dlg.line_edit_auth_id.text().strip() if repo_name in self.repository_manager.directories: repo_name += "(2)" # Show progress dialog self.show_progress_dialog("Fetching repository's metadata") # Add repository try: status, adderror = self.repository_manager.add_directory( repo_name, repo_url, repo_auth_cfg) if status: self.message_bar.pushMessage( self.tr("Repository was successfully added"), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr("Unable to add repository: %s") % adderror, Qgis.Warning, 5) except Exception as e: self.message_bar.pushMessage(self.tr("%s") % e, Qgis.Warning, 5) finally: self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() # Deactivate edit and delete button self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def edit_repository(self): """Open edit repository dialog.""" selected_item = self.tree_repositories.currentItem() if selected_item: repo_name = selected_item.text(0) if not repo_name: return # Check if it is among the officially approved QGIS repositories settings = QgsSettings() settings.beginGroup(repo_settings_group()) if (settings.value(repo_name + "/url") in self.repository_manager._online_directories.values()): self.message_bar.pushMessage( self.tr("You can not edit the official repositories!"), Qgis.Warning, 5) return dlg = ManageRepositoryDialog(self) dlg.line_edit_name.setText(repo_name) dlg.line_edit_url.setText( self.repository_manager.directories[repo_name]["url"]) dlg.line_edit_auth_id.setText( self.repository_manager.directories[repo_name]["auth_cfg"]) if not dlg.exec_(): return # Check if the changed URL is already present and that # the new repository name is unique new_url = dlg.line_edit_url.text().strip() old_url = self.repository_manager.directories[repo_name]["url"] new_name = dlg.line_edit_name.text().strip() for repoName, repo in self.repository_manager.directories.items(): if new_url == repo["url"] and (old_url != new_url): self.message_bar.pushMessage( self.tr("Unable to add another repository with the same " "URL!"), Qgis.Warning, 5, ) return if new_name == repoName and (repo_name != new_name): self.message_bar.pushMessage( self.tr("Repositories must have unique names!"), Qgis.Warning, 5) return # Redundant if (new_name in self.repository_manager.directories) and (new_name != repo_name): new_name += "(2)" new_auth_cfg = dlg.line_edit_auth_id.text() # Show progress dialog self.show_progress_dialog("Fetching repository's metadata") # Edit repository try: status, editerror = self.repository_manager.edit_directory( repo_name, new_name, old_url, new_url, new_auth_cfg) if status: self.message_bar.pushMessage( self.tr("Repository is successfully updated"), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr("Unable to edit repository: %s") % editerror, Qgis.Warning, 5, ) except Exception as e: self.message_bar.pushMessage(self.tr("%s") % e, Qgis.Warning, 5) finally: self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() # Deactivate the edit and delete buttons self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def delete_repository(self): """Delete a repository in the tree widget.""" selected_item = self.tree_repositories.currentItem() if selected_item: repo_name = selected_item.text(0) if not repo_name: return # Check if it is among the offical repositories repo_url = self.repository_manager.directories[repo_name]["url"] if repo_url in self.repository_manager._online_directories.values(): self.message_bar.pushMessage( self.tr("You can not remove official repositories!"), Qgis.Warning, 5) return warning = (self.tr("Are you sure you want to remove the following " "repository?") + "\n" + repo_name) if (QMessageBox.warning( self, self.tr("QGIS Resource Sharing"), warning, QMessageBox.Yes, QMessageBox.No, ) == QMessageBox.No): return # Remove repository installed_collections = self.collection_manager.get_installed_collections( repo_url) if installed_collections: message = ("You have installed collections from this " "repository. Please uninstall them first!") self.message_bar.pushMessage(message, Qgis.Warning, 5) else: self.repository_manager.remove_directory(repo_name) # Reload data and widget self.reload_data_and_widget() # Deactivate the edit and delete buttons self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def reload_off_res_directory(self): """Slot called when the user clicks the 'Reload directory' button.""" # Show progress dialog self.show_progress_dialog("Reloading the official QGIS resource" " directory") self.repository_manager._online_directories = {} # Registered directories self.repository_manager._directories = {} self.repository_manager.fetch_online_directories() # Load directory of repositories from settings self.repository_manager.load_directories() self.message_bar.pushMessage("On-line directory reloaded", Qgis.Info, 5) self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() def reload_repositories(self): """Slot called when the user clicks the 'Reload repositories' button.""" # Show progress dialog self.show_progress_dialog("Reloading all repositories") for repo_name in self.repository_manager.directories: directory = self.repository_manager.directories[repo_name] url = directory["url"] auth_cfg = directory["auth_cfg"] try: status, reloaderror = self.repository_manager.reload_directory( repo_name, url, auth_cfg) if status: self.message_bar.pushMessage( self.tr("Repository %s is successfully reloaded") % repo_name, Qgis.Info, 5, ) else: self.message_bar.pushMessage( self.tr("Unable to reload %s: %s") % (repo_name, reloaderror), Qgis.Warning, 5, ) except Exception as e: self.message_bar.pushMessage( self.tr("%s") % e, Qgis.Warning, 5) self.progress_dialog.hide() # Reload data and widget self.reload_data_and_widget() def install_collection(self): """Slot called when the user clicks the Install/Reinstall button.""" # Save the current index to enable selection after installation self.current_index = self.list_view_collections.currentIndex() self.show_progress_dialog("Starting installation...") self.progress_dialog.canceled.connect(self.install_canceled) self.installer_thread = QThread() self.installer_worker = CollectionInstaller(self.collection_manager, self._sel_coll_id) self.installer_worker.moveToThread(self.installer_thread) self.installer_worker.finished.connect(self.install_finished) self.installer_worker.aborted.connect(self.install_aborted) self.installer_worker.progress.connect(self.install_progress) self.installer_thread.started.connect(self.installer_worker.run) self.installer_thread.start() def install_finished(self): # Process the result self.progress_dialog.hide() installStatus = self.installer_worker.install_status if not installStatus: message = self.installer_worker.error_message # Clean up the worker and thread self.installer_worker.deleteLater() self.installer_thread.quit() self.installer_thread.wait() self.installer_thread.deleteLater() if installStatus: self.reload_collections_model() # Report what has been installed message = "<b>%s</b> was successfully installed, " "containing:\n<ul>" % ( config.COLLECTIONS[self._sel_coll_id]["name"]) number = 0 for type_, description in SUPPORTED_RESOURCES_MAP.items(): if type_ in config.COLLECTIONS[self._sel_coll_id].keys(): number = config.COLLECTIONS[self._sel_coll_id][type_] message += (f"\n<li>{number} {description}" f'{"s" if number > 1 else ""}' f"</li>") message += "\n</ul>" QMessageBox.information(self, "Resource Sharing", message) self.populate_repositories_widget() # Set the selection oldRow = self.current_index.row() newIndex = self.collections_model.createIndex(oldRow, 0) selection_model = self.list_view_collections.selectionModel() selection_model.setCurrentIndex(newIndex, selection_model.ClearAndSelect) selection_model.select(newIndex, selection_model.ClearAndSelect) # Update the buttons self.button_install.setEnabled(True) self.button_install.setText("Reinstall") self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) self.show_collection_metadata(self._sel_coll_id) def install_canceled(self): self.progress_dialog.hide() self.show_progress_dialog("Cancelling installation...") self.installer_worker.abort() def install_aborted(self): if self.installer_thread.isRunning(): self.installer_thread.quit() self.installer_thread.finished.connect(self.progress_dialog.hide) def install_progress(self, text): self.progress_dialog.setLabelText(text) def uninstall_collection(self): """Slot called when the user clicks the 'Uninstall' button.""" # get the QModelIndex for the item to be uninstalled uninstall_index = self.list_view_collections.currentIndex() coll_id = self._sel_coll_id try: self.collection_manager.uninstall(coll_id) except Exception as e: LOGGER.error("Could not uninstall collection " + config.COLLECTIONS[coll_id]["name"] + ":\n" + str(e)) else: QMessageBox.information( self, "Resource Sharing", "The collection was successfully uninstalled!") self.reload_collections_model() # Fix the GUI currentMenuRow = self.menu_list_widget.currentRow() self.set_current_tab(currentMenuRow) self.populate_repositories_widget() rowCount = self.collection_proxy.rowCount() if rowCount > 0: # Set the current (and selected) row in the listview newRow = uninstall_index.row() # Check if this was the last element rowCount = self.collection_proxy.rowCount() if newRow == rowCount: newRow = newRow - 1 # Select the new current element newIndex = self.collections_model.createIndex(newRow, 0) selection_model = self.list_view_collections.selectionModel() selection_model.setCurrentIndex(newIndex, selection_model.ClearAndSelect) # Get the id of the current collection proxyModel = self.list_view_collections.model() proxyIndex = proxyModel.index(newRow, 0) current_coll_id = proxyIndex.data(COLLECTION_ID_ROLE) self._sel_coll_id = current_coll_id # Update buttons status = config.COLLECTIONS[current_coll_id]["status"] if status == COLLECTION_INSTALLED_STATUS: self.button_install.setEnabled(True) self.button_install.setText("Reinstall") self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) else: self.button_install.setEnabled(True) self.button_install.setText("Install") self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Update the web_view_details frame self.show_collection_metadata(current_coll_id) else: self.button_install.setEnabled(False) self.button_install.setText("Install") self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) def open_collection(self): """Slot called when the user clicks the 'Open' button.""" collection_path = local_collection_path(self._sel_coll_id) directory_url = QUrl.fromLocalFile(str(collection_path)) QDesktopServices.openUrl(directory_url) def reload_data_and_widget(self): """Reload repositories and collections and update widgets related.""" self.reload_repositories_widget() self.reload_collections_model() def reload_repositories_widget(self): """Refresh tree repositories using new repositories data.""" self.repository_manager.load_directories() self.populate_repositories_widget() def populate_repositories_widget(self): """Populate the current dictionary repositories to the tree widget.""" # Clear the current tree widget self.tree_repositories.clear() installed_collections = self.collection_manager.get_installed_collections( ) # Export the updated ones from the repository manager repo_Font = QFont() repo_with_installed_Font = QFont() repo_with_installed_Font.setWeight(60) collection_brush = QBrush(Qt.darkGray) installed_collection_brush = QBrush(QColor(60, 25, 10)) for repo_name in self.repository_manager.directories: url = self.repository_manager.directories[repo_name]["url"] item = QTreeWidgetItem(self.tree_repositories, REPOSITORY_ITEM) # Is the repository in the QGIS resource directory? if url in self.repository_manager._online_directories.values(): repo_with_installed_Font.setUnderline(True) repo_Font.setUnderline(True) else: repo_with_installed_Font.setUnderline(False) repo_Font.setUnderline(False) item.setText(0, repo_name) item.setText(1, url) item.setFont(0, repo_Font) for coll_id in config.COLLECTIONS: if ("repository_name" in config.COLLECTIONS[coll_id].keys() and config.COLLECTIONS[coll_id]["repository_name"] == repo_name): coll_name = config.COLLECTIONS[coll_id]["name"] coll_tags = config.COLLECTIONS[coll_id]["tags"] collectionItem = QTreeWidgetItem(item, COLLECTION_ITEM) brush = collection_brush collectionFont = QFont() collectionFont.setStyle(QFont.StyleItalic) collitemtext = coll_name if (installed_collections and coll_id in installed_collections.keys()): collitemtext = coll_name + " (installed)" brush = installed_collection_brush item.setFont(0, repo_with_installed_Font) item.setForeground(0, brush) item.setForeground(1, brush) collectionItem.setFont(0, collectionFont) collectionItem.setForeground(0, brush) collectionItem.setText(0, collitemtext) collectionItem.setFont(1, collectionFont) collectionItem.setForeground(1, brush) collectionItem.setText(1, coll_tags) self.tree_repositories.resizeColumnToContents(0) self.tree_repositories.resizeColumnToContents(1) self.tree_repositories.sortItems(1, Qt.AscendingOrder) def reload_collections_model(self): """Reload the collections model with the current collections.""" self.collections_model.clear() installed_collections = self.collection_manager.get_installed_collections( ) for id in config.COLLECTIONS: collection_name = config.COLLECTIONS[id]["name"] collection_author = config.COLLECTIONS[id]["author"] collection_tags = config.COLLECTIONS[id]["tags"] collection_description = config.COLLECTIONS[id]["description"] collection_status = config.COLLECTIONS[id]["status"] repository_name = "" if "repository_name" in config.COLLECTIONS[id].keys(): repository_name = config.COLLECTIONS[id]["repository_name"] item = QStandardItem(collection_name + " (" + repository_name + ")") item.setEditable(False) item.setData(id, COLLECTION_ID_ROLE) item.setData(collection_name, COLLECTION_NAME_ROLE) item.setData(collection_description, COLLECTION_DESCRIPTION_ROLE) item.setData(collection_author, COLLECTION_AUTHOR_ROLE) item.setData(collection_tags, COLLECTION_TAGS_ROLE) item.setData(collection_status, COLLECTION_STATUS_ROLE) # Make installed collections stand out if installed_collections and id in installed_collections.keys(): collectionFont = QFont() collectionFont.setWeight(60) item.setFont(collectionFont) self.collections_model.appendRow(item) self.collections_model.sort(0, Qt.AscendingOrder) def on_tree_repositories_itemSelectionChanged(self): """Slot for the itemSelectionChanged signal of tree_repositories.""" selected_item = self.tree_repositories.currentItem() if selected_item and selected_item.type() == REPOSITORY_ITEM: if selected_item: repo_name = selected_item.text(0) if not repo_name: return if repo_name not in self.repository_manager.directories.keys(): return repo_url = self.repository_manager.directories[repo_name]["url"] # Disable the edit and delete buttons for "official" repositories if repo_url in self.repository_manager._online_directories.values( ): self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) else: # Activate the edit and delete buttons self.button_edit.setEnabled(True) self.button_delete.setEnabled(True) elif selected_item and selected_item.type() == COLLECTION_ITEM: self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) else: self.button_edit.setEnabled(False) self.button_delete.setEnabled(False) def on_list_view_collections_clicked(self, index): """Slot called when the user clicks an item in list_view_collections.""" real_index = self.collection_proxy.mapToSource(index) if real_index.row() != -1: collection_item = self.collections_model.itemFromIndex(real_index) collection_id = collection_item.data(COLLECTION_ID_ROLE) self._sel_coll_id = collection_id # Enable / disable buttons status = config.COLLECTIONS[self._sel_coll_id]["status"] is_installed = status == COLLECTION_INSTALLED_STATUS if is_installed: self.button_install.setEnabled(True) self.button_install.setText("Reinstall") self.button_open.setEnabled(True) self.button_uninstall.setEnabled(True) else: self.button_install.setEnabled(True) self.button_install.setText("Install") self.button_open.setEnabled(False) self.button_uninstall.setEnabled(False) # Show metadata self.show_collection_metadata(collection_id) @pyqtSlot(str) def filter_collections(self, text): search = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp) self.collection_proxy.setFilterRegExp(search) def show_collection_metadata(self, id): """Show the collection metadata given the ID.""" html = self.collection_manager.get_html(id) self.web_view_details.setHtml(html) def reject(self): """Slot called when the dialog is closed.""" # Serialize collections to settings self.repository_manager.serialize_repositories() self.done(0) def open_help(self): """Open help.""" doc_url = QUrl("http://qgis-contribution.github.io/" + "QGIS-ResourceSharing/") QDesktopServices.openUrl(doc_url) def show_progress_dialog(self, text): """Show infinite progress dialog with given text. :param text: Text as the label of the progress dialog :type text: str """ if self.progress_dialog is None: self.progress_dialog = QProgressDialog(self) self.progress_dialog.setWindowModality(Qt.WindowModal) self.progress_dialog.setAutoClose(False) title = self.tr("Resource Sharing") self.progress_dialog.setWindowTitle(title) # Just use an infinite progress bar here self.progress_dialog.setMaximum(0) self.progress_dialog.setMinimum(0) self.progress_dialog.setValue(0) self.progress_dialog.setLabelText(text) self.progress_dialog.show()