class PROCESS(QObject): queue_finished = pyqtSignal(int) prog_finished = pyqtSignal(tuple) stoped = pyqtSignal(int, int) # 1: start, 0: finished, 2: error | queue state = pyqtSignal(int, str) def __init__(self, parent=None): QObject.__init__(self, parent) self.process = QProcess() self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.finished.connect(self.emit_finished) self.process.stateChanged.connect(self.emit_state) self.process_type = 0 # 0 process, 1 runnable self.threadpool = QThreadPool() self.worker = Worker() self.worker.signals.state.connect(self.emit_state) self.queue = None def emit_state(self, state): self.state.emit(state, self.prog_name) def emit_finished(self, exitcode, exitstatus): self.prog_finished.emit((self.prog_name, exitcode, exitstatus)) def set_queue(self, queue): self.queue = queue def start_process(self): try: obj = self.queue.get(False) self.prog_name = obj.keys()[0] if callable(obj.values()[0][0]): self.process_type = 1 funct = obj.values()[0][0] args = obj.values()[0][1] self.worker.insert_function(funct, args, self.prog_name) self.threadpool.start(self.worker) else: self.process_type = 0 self.process.start(obj.values()[0][0], obj.values()[0][1]) except Queue.Empty: self.queue_finished.emit(self.queue.name) def force_finished(self): # for process (programs) self.stoped.emit(1, self.queue.name) if self.process_type == 0: self.process.terminate() if not self.process.waitForFinished(1000): self.process.kill() else: if self.threadpool.activeThreadCount(): self.threadpool.clear() self.threadpool.waitForDone() with self.queue.mutex: self.queue.queue.clear() self.stoped.emit(0, self.queue.name)
class CatalogDialogTool(QObject): """ Tool for managing the search and export functionality """ def __init__(self, iface, dialog_ui, bbox_tool): """ Constructor for the dialog tool :param iface: The QGIS Interface :param dialog_ui: The dialog GUI :param bbox_tool The bounding box tool :return: dialog tool """ QObject.__init__(self, None) self.iface = iface self.dialog_ui = dialog_ui self.bbox_tool = bbox_tool self.progress_bar = None self.progress_message_bar = None self.progress_message_bar_widget = None self.search_thread_pool = QThreadPool() self.search_lock = Lock() self.export_thread_pool = QThreadPool() self.export_lock = Lock() self.query = None self.previous_credentials = None self.export_file = None self.footprint_layer = None self.filters = CatalogFilters(self.dialog_ui) self.dialog_ui.aoi_button.clicked.connect(self.aoi_button_clicked) self.dialog_ui.reset_button.clicked.connect(self.reset_button_clicked) self.dialog_ui.export_button.clicked.connect(self.export_button_clicked) self.bbox_tool.released.connect(self.search) self.model = None def init_progress_bar(self, progress_max): """ Sets up the progress bar for search functionality :return: None """ if not self.progress_message_bar: self.progress_message_bar = self.iface.messageBar().createMessage("Querying for data") self.progress_bar = QProgressBar() self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(progress_max) self.progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignCenter) self.progress_message_bar.layout().addWidget(self.progress_bar) self.progress_message_bar_widget = self.iface.messageBar().pushWidget(self.progress_message_bar, self.iface.messageBar().INFO) def init_layers(self): """ Sets up the layers for rendering the items :return: None """ if self.footprint_layer: QgsMapLayerRegistry.instance().removeMapLayer(self.footprint_layer.id()) self.footprint_layer = QgsVectorLayer("Polygon?crs=EPSG:4326", "Catalog Footprints", "memory") self.footprint_layer.setCrs(QgsCoordinateReferenceSystem(4326), True) self.footprint_layer.dataProvider().addAttributes(CatalogAcquisitionFeature.get_fields()) QgsMapLayerRegistry.instance().addMapLayer(self.footprint_layer) def clear_widgets(self): """ Clears the progress bar :return: None """ self.progress_bar = None self.progress_message_bar = None if self.progress_message_bar_widget: self.iface.messageBar().popWidget(self.progress_message_bar_widget) self.progress_message_bar_widget = None def is_searching(self): """ Check to see if the system is still searching (checks if there's work in the search thread pool) :return: True if searching; False otherwise """ return self.get_search_active_thread_count() > 0 def is_exporting(self): """ Check to see if the system is still exporting (checks if there's work in the export thread pool) :return: True if searching; False otherwise """ return self.get_export_active_thread_count() > 0 def get_search_active_thread_count(self): """ Gets the number of active threads in the search thread pool :return: """ with self.search_lock: return self.search_thread_pool.activeThreadCount() def get_export_active_thread_count(self): """ Gets the number of active threads in the export thread pool :return: """ with self.export_lock: return self.export_thread_pool.activeThreadCount() def aoi_button_clicked(self): """ Validates and runs the search if validation successful :return: None """ # can't run search during export if self.is_exporting(): self.iface.messageBar().pushMessage("Error", "Cannot run search while export is running.", level=QgsMessageBar.CRITICAL) # can't run multiple search elif self.is_searching(): self.iface.messageBar().pushMessage("Error", "Cannot run a new search while a search is running.", level=QgsMessageBar.CRITICAL) else: self.bbox_tool.reset() self.iface.mapCanvas().setMapTool(self.bbox_tool) def reset_button_clicked(self): """ Resets filters. :return: None """ self.reset() def export_button_clicked(self): """ Validates and runs the export if validation successful :return: None """ # can't run export during search if self.is_searching(): self.iface.messageBar().pushMessage("Error", "Cannot run export while search is running.", level=QgsMessageBar.CRITICAL) # can't run multiple exports elif self.is_exporting(): self.iface.messageBar().pushMessage("Error", "Cannot run a new export while a export is running.", level=QgsMessageBar.CRITICAL) else: self.export() def search(self, top, bottom, left, right): self.search_thread_pool.waitForDone(0) # validate credentials if they changed errors = [] username, password, api_key, max_items_to_return = SettingsOps.get_settings() credentials = [username, password, api_key] if not self.previous_credentials or self.previous_credentials != credentials: SettingsOps.validate_stored_info(username, password, api_key, max_items_to_return, errors) self.previous_credentials = credentials # validate filters if not errors: self.filters.validate(errors) if errors: self.iface.messageBar().pushMessage("Error", "The following errors occurred: " + "<br />".join(errors), level=QgsMessageBar.CRITICAL) else: self.init_layers() self.dialog_ui.tab_widget.setCurrentIndex(RESULTS_TAB_INDEX) next_x_list = self.drange_list(float(left) + INCREMENTAL_INTERVAL, float(right), INCREMENTAL_INTERVAL) next_y_list = self.drange_list(float(bottom) + INCREMENTAL_INTERVAL, float(top), INCREMENTAL_INTERVAL) self.init_progress_bar(len(next_x_list) * len(next_y_list)) self.model = CatalogTableModel(self.dialog_ui.table_view) self.dialog_ui.table_view.setModel(self.model) self.dialog_ui.table_view.selectionModel().selectionChanged.connect(self.selection_changed) if not self.query: self.query = GBDQuery(username=username, password=password, api_key=api_key) filters = self.filters.get_query_filters() time_begin = self.filters.get_datetime_begin() time_end = self.filters.get_datetime_end() current_x = float(left) current_y = float(bottom) for next_x in next_x_list: for next_y in next_y_list: search_runnable = CatalogSearchRunnable(self.query, self.model, self, top=next_y, left=current_x, right=next_x, bottom=current_y, time_begin=time_begin, time_end=time_end, filters=filters) search_runnable.task_object.task_complete.connect(self.on_search_complete) self.search_thread_pool.start(search_runnable) current_y = next_y current_y = bottom current_x = next_x def reset(self): self.filters.remove_all() def export(self): self.export_thread_pool.waitForDone(0) acquisitions = None if self.model is not None: acquisitions = self.model.data if not acquisitions: self.iface.messageBar().pushMessage("Error", "No data to export.", level=QgsMessageBar.CRITICAL) else: # open file ui select_file_ui = QFileDialog() starting_file = self.export_file or os.path.expanduser("~") export_file = select_file_ui.getSaveFileName(None, "Choose output file", starting_file, SELECT_FILTER) if export_file: self.export_file = export_file self.init_progress_bar(0) export_runnable = CatalogExportRunnable(acquisitions, self.export_file) export_runnable.task_object.task_complete.connect(self.on_export_complete) self.export_thread_pool.start(export_runnable) @pyqtSlot() def on_search_complete(self): thread_count = self.get_search_active_thread_count() if self.progress_message_bar: self.progress_bar.setValue(self.progress_bar.value() + 1) if thread_count == 0: self.clear_widgets() self.dialog_ui.table_view.resizeColumnsToContents() @pyqtSlot() def on_export_complete(self): thread_count = self.get_export_active_thread_count() if self.progress_message_bar: self.progress_bar.setValue(self.progress_bar.value() + 1) if thread_count == 0: self.clear_widgets() self.iface.messageBar().pushMessage("Info", 'File export has completed to "%s".' % self.export_file) def selection_changed(self, selected, deselected): self.footprint_layer.startEditing() # draw footprints for selected rows selected_rows = set() for index in selected.indexes(): selected_rows.add(index.row()) for row in selected_rows: acquisition = self.model.get(row) feature_id = self.model.generate_feature_id() self.model.set_feature_id(acquisition, feature_id) feature = CatalogAcquisitionFeature(feature_id, acquisition) self.footprint_layer.dataProvider().addFeatures([feature]) # remove footprints for deselected rows deselected_rows = set() for index in deselected.indexes(): deselected_rows.add(index.row()) feature_ids_to_remove = [] for row in deselected_rows: acquisition = self.model.get(row) feature_id = self.model.get_feature_id(acquisition) feature_ids_to_remove.append(feature_id) self.model.remove_feature_id(acquisition) if feature_ids_to_remove: self.footprint_layer.dataProvider().deleteFeatures(feature_ids_to_remove) self.footprint_layer.commitChanges() self.footprint_layer.updateExtents() self.footprint_layer.triggerRepaint() def drange_list(self, start, stop, step): drange_list = [] r = start while r < stop: drange_list.append(r) r += step if not drange_list: drange_list.append(stop) return drange_list
class CatalogDialogTool(QObject): """ Tool for managing the search and export functionality """ def __init__(self, iface, dialog_ui, bbox_tool): """ Constructor for the dialog tool :param iface: The QGIS Interface :param dialog_ui: The dialog GUI :param bbox_tool The bounding box tool :return: dialog tool """ QObject.__init__(self, None) self.iface = iface self.dialog_ui = dialog_ui self.bbox_tool = bbox_tool self.progress_bar = None self.progress_message_bar = None self.progress_message_bar_widget = None self.search_thread_pool = QThreadPool() self.search_lock = Lock() self.export_thread_pool = QThreadPool() self.export_lock = Lock() self.query = None self.previous_credentials = None self.export_file = None self.footprint_layer = None self.filters = CatalogFilters(self.dialog_ui) self.dialog_ui.aoi_button.clicked.connect(self.aoi_button_clicked) self.dialog_ui.reset_button.clicked.connect(self.reset_button_clicked) self.dialog_ui.export_button.clicked.connect( self.export_button_clicked) self.bbox_tool.released.connect(self.search) self.model = None def init_progress_bar(self, progress_max): """ Sets up the progress bar for search functionality :return: None """ if not self.progress_message_bar: self.progress_message_bar = self.iface.messageBar().createMessage( "Querying for data") self.progress_bar = QProgressBar() self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(progress_max) self.progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignCenter) self.progress_message_bar.layout().addWidget(self.progress_bar) self.progress_message_bar_widget = self.iface.messageBar( ).pushWidget(self.progress_message_bar, self.iface.messageBar().INFO) def init_layers(self): """ Sets up the layers for rendering the items :return: None """ if self.footprint_layer: QgsMapLayerRegistry.instance().removeMapLayer( self.footprint_layer.id()) self.footprint_layer = QgsVectorLayer("Polygon?crs=EPSG:4326", "Catalog Footprints", "memory") self.footprint_layer.setCrs(QgsCoordinateReferenceSystem(4326), True) self.footprint_layer.dataProvider().addAttributes( CatalogAcquisitionFeature.get_fields()) QgsMapLayerRegistry.instance().addMapLayer(self.footprint_layer) def clear_widgets(self): """ Clears the progress bar :return: None """ self.progress_bar = None self.progress_message_bar = None if self.progress_message_bar_widget: self.iface.messageBar().popWidget(self.progress_message_bar_widget) self.progress_message_bar_widget = None def is_searching(self): """ Check to see if the system is still searching (checks if there's work in the search thread pool) :return: True if searching; False otherwise """ return self.get_search_active_thread_count() > 0 def is_exporting(self): """ Check to see if the system is still exporting (checks if there's work in the export thread pool) :return: True if searching; False otherwise """ return self.get_export_active_thread_count() > 0 def get_search_active_thread_count(self): """ Gets the number of active threads in the search thread pool :return: """ with self.search_lock: return self.search_thread_pool.activeThreadCount() def get_export_active_thread_count(self): """ Gets the number of active threads in the export thread pool :return: """ with self.export_lock: return self.export_thread_pool.activeThreadCount() def aoi_button_clicked(self): """ Validates and runs the search if validation successful :return: None """ # can't run search during export if self.is_exporting(): self.iface.messageBar().pushMessage( "Error", "Cannot run search while export is running.", level=QgsMessageBar.CRITICAL) # can't run multiple search elif self.is_searching(): self.iface.messageBar().pushMessage( "Error", "Cannot run a new search while a search is running.", level=QgsMessageBar.CRITICAL) else: self.bbox_tool.reset() self.iface.mapCanvas().setMapTool(self.bbox_tool) def reset_button_clicked(self): """ Resets filters. :return: None """ self.reset() def export_button_clicked(self): """ Validates and runs the export if validation successful :return: None """ # can't run export during search if self.is_searching(): self.iface.messageBar().pushMessage( "Error", "Cannot run export while search is running.", level=QgsMessageBar.CRITICAL) # can't run multiple exports elif self.is_exporting(): self.iface.messageBar().pushMessage( "Error", "Cannot run a new export while a export is running.", level=QgsMessageBar.CRITICAL) else: self.export() def search(self, top, bottom, left, right): self.search_thread_pool.waitForDone(0) # validate credentials if they changed errors = [] username, password, api_key, max_items_to_return = SettingsOps.get_settings( ) credentials = [username, password, api_key] if not self.previous_credentials or self.previous_credentials != credentials: SettingsOps.validate_stored_info(username, password, api_key, max_items_to_return, errors) self.previous_credentials = credentials # validate filters if not errors: self.filters.validate(errors) if errors: self.iface.messageBar().pushMessage( "Error", "The following errors occurred: " + "<br />".join(errors), level=QgsMessageBar.CRITICAL) else: self.init_layers() self.dialog_ui.tab_widget.setCurrentIndex(RESULTS_TAB_INDEX) next_x_list = self.drange_list( float(left) + INCREMENTAL_INTERVAL, float(right), INCREMENTAL_INTERVAL) next_y_list = self.drange_list( float(bottom) + INCREMENTAL_INTERVAL, float(top), INCREMENTAL_INTERVAL) self.init_progress_bar(len(next_x_list) * len(next_y_list)) self.model = CatalogTableModel(self.dialog_ui.table_view) self.dialog_ui.table_view.setModel(self.model) self.dialog_ui.table_view.selectionModel( ).selectionChanged.connect(self.selection_changed) if not self.query: self.query = GBDQuery(username=username, password=password, api_key=api_key) filters = self.filters.get_query_filters() time_begin = self.filters.get_datetime_begin() time_end = self.filters.get_datetime_end() current_x = float(left) current_y = float(bottom) for next_x in next_x_list: for next_y in next_y_list: search_runnable = CatalogSearchRunnable( self.query, self.model, self, top=next_y, left=current_x, right=next_x, bottom=current_y, time_begin=time_begin, time_end=time_end, filters=filters) search_runnable.task_object.task_complete.connect( self.on_search_complete) self.search_thread_pool.start(search_runnable) current_y = next_y current_y = bottom current_x = next_x def reset(self): self.filters.remove_all() def export(self): self.export_thread_pool.waitForDone(0) acquisitions = None if self.model is not None: acquisitions = self.model.data if not acquisitions: self.iface.messageBar().pushMessage("Error", "No data to export.", level=QgsMessageBar.CRITICAL) else: # open file ui select_file_ui = QFileDialog() starting_file = self.export_file or os.path.expanduser("~") export_file = select_file_ui.getSaveFileName( None, "Choose output file", starting_file, SELECT_FILTER) if export_file: self.export_file = export_file self.init_progress_bar(0) export_runnable = CatalogExportRunnable( acquisitions, self.export_file) export_runnable.task_object.task_complete.connect( self.on_export_complete) self.export_thread_pool.start(export_runnable) @pyqtSlot() def on_search_complete(self): thread_count = self.get_search_active_thread_count() if self.progress_message_bar: self.progress_bar.setValue(self.progress_bar.value() + 1) if thread_count == 0: self.clear_widgets() self.dialog_ui.table_view.resizeColumnsToContents() @pyqtSlot() def on_export_complete(self): thread_count = self.get_export_active_thread_count() if self.progress_message_bar: self.progress_bar.setValue(self.progress_bar.value() + 1) if thread_count == 0: self.clear_widgets() self.iface.messageBar().pushMessage( "Info", 'File export has completed to "%s".' % self.export_file) def selection_changed(self, selected, deselected): self.footprint_layer.startEditing() # draw footprints for selected rows selected_rows = set() for index in selected.indexes(): selected_rows.add(index.row()) for row in selected_rows: acquisition = self.model.get(row) feature_id = self.model.generate_feature_id() self.model.set_feature_id(acquisition, feature_id) feature = CatalogAcquisitionFeature(feature_id, acquisition) self.footprint_layer.dataProvider().addFeatures([feature]) # remove footprints for deselected rows deselected_rows = set() for index in deselected.indexes(): deselected_rows.add(index.row()) feature_ids_to_remove = [] for row in deselected_rows: acquisition = self.model.get(row) feature_id = self.model.get_feature_id(acquisition) feature_ids_to_remove.append(feature_id) self.model.remove_feature_id(acquisition) if feature_ids_to_remove: self.footprint_layer.dataProvider().deleteFeatures( feature_ids_to_remove) self.footprint_layer.commitChanges() self.footprint_layer.updateExtents() self.footprint_layer.triggerRepaint() def drange_list(self, start, stop, step): drange_list = [] r = start while r < stop: drange_list.append(r) r += step if not drange_list: drange_list.append(stop) return drange_list