def _fill_tbl(self, filter_name=""): """ Fill table with initial data into QTableView """ complet_list = self._get_list("v_ui_workspace", filter_name) if complet_list is False: return False, False for field in complet_list['body']['data']['fields']: if 'hidden' in field and field['hidden']: continue model = self.tbl_wrkspcm.model() if model is None: model = QStandardItemModel() self.tbl_wrkspcm.setModel(model) model.removeRows(0, model.rowCount()) if field['value']: self.tbl_wrkspcm = tools_gw.add_tableview_header( self.tbl_wrkspcm, field) self.tbl_wrkspcm = tools_gw.fill_tableview_rows( self.tbl_wrkspcm, field) # TODO: config_form_tableview # widget = tools_gw.set_tablemodel_config(self.dlg_workspace_manager, self.tbl_wrkspcm, 'tbl_wrkspcm', 1, True) tools_qt.set_tableview_config(self.tbl_wrkspcm) return complet_list
class HTMLFieldSelectionDialog(QDialog, HTML_FIELDS_CLASS): def __init__(self, iface, feat): super(HTMLFieldSelectionDialog, self).__init__(iface.mainWindow()) self.setupUi(self) self.iface = iface self.feat = feat self.selected = [] self.selectAllButton.clicked.connect(self.selectAll) self.clearButton.clicked.connect(self.clearAll) self.checkBox.stateChanged.connect(self.initModel) self.initModel() def initModel(self): self.model = QStandardItemModel(self.listView) state = self.checkBox.isChecked() for key in list(self.feat.keys()): if state == False or self.feat[key] > 0: item = QStandardItem() item.setText(key) item.setCheckable(True) item.setSelectable(False) self.model.appendRow(item) self.listView.setModel(self.model) self.listView.show() def selectAll(self): cnt = self.model.rowCount() for i in range(0, cnt): item = self.model.item(i) item.setCheckState(Qt.Checked) def clearAll(self): cnt = self.model.rowCount() for i in range(0, cnt): item = self.model.item(i) item.setCheckState(Qt.Unchecked) def accept(self): self.selected = [] cnt = self.model.rowCount() for i in range(0, cnt): item = self.model.item(i) if item.checkState() == Qt.Checked: self.selected.append(item.text()) self.close()
class SimulationOverview(uicls, basecls): """Dialog with methods for handling running simulations.""" PROGRESS_COLUMN_IDX = 2 def __init__(self, plugin_dock, parent=None): super().__init__(parent) self.setupUi(self) self.plugin_dock = plugin_dock self.threedi_api = self.plugin_dock.threedi_api self.user = self.plugin_dock.current_user self.model_selection_dlg = ModelSelectionDialog(self.plugin_dock, parent=self) self.simulation_init_wizard = None self.simulation_wizard = None self.simulations_keys = {} self.last_progresses = {} self.simulations_without_progress = set() self.tv_model = None self.setup_view_model() self.plugin_dock.simulations_progresses_sentinel.progresses_fetched.connect( self.update_progress) self.pb_new_sim.clicked.connect(self.new_wizard_init) self.pb_stop_sim.clicked.connect(self.stop_simulation) def setup_view_model(self): """Setting up model and columns for TreeView.""" delegate = SimulationProgressDelegate(self.tv_sim_tree) self.tv_sim_tree.setItemDelegateForColumn(self.PROGRESS_COLUMN_IDX, delegate) self.tv_model = QStandardItemModel(0, 3) self.tv_model.setHorizontalHeaderLabels( ["Simulation name", "User", "Progress"]) self.tv_sim_tree.setModel(self.tv_model) def add_simulation_to_model(self, simulation, status, progress): """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) progress_item = QStandardItem() progress_item.setData((status, progress), PROGRESS_ROLE) self.tv_model.appendRow([sim_name_item, user_item, progress_item]) self.simulations_keys[sim_id] = simulation for i in range(self.PROGRESS_COLUMN_IDX): self.tv_sim_tree.resizeColumnToContents(i) def update_progress(self, progresses): """Updating progress bars in the running simulations list.""" for sim_id, (sim, status, progress) in progresses.items(): status_name = status.name if status_name not in [ "queued", "starting", "initialized", "postprocessing" ]: continue if sim_id not in self.simulations_keys: self.add_simulation_to_model(sim, status, progress) row_count = self.tv_model.rowCount() for row_idx in range(row_count): name_item = self.tv_model.item(row_idx, 0) sim_id = name_item.data(Qt.UserRole) if sim_id in self.simulations_without_progress or sim_id not in progresses: continue progress_item = self.tv_model.item(row_idx, self.PROGRESS_COLUMN_IDX) sim, new_status, new_progress = progresses[sim_id] status_name = new_status.name if status_name == "stopped" or status_name == "crashed": old_status, old_progress = progress_item.data(PROGRESS_ROLE) progress_item.setData((new_status, old_progress), PROGRESS_ROLE) self.simulations_without_progress.add(sim_id) else: progress_item.setData((new_status, new_progress), PROGRESS_ROLE) if status_name == "finished": self.simulations_without_progress.add(sim_id) msg = f"Simulation {sim.name} finished!" self.plugin_dock.communication.bar_info(msg, log_text_color=QColor( Qt.darkGreen)) def new_wizard_init(self): """Open new simulation initiation options dialog.""" if self.plugin_dock.current_local_schematisation is not None: self.model_selection_dlg.search_le.setText( self.plugin_dock.current_local_schematisation.name) self.model_selection_dlg.fetch_3di_models() self.model_selection_dlg.refresh_templates_list() self.model_selection_dlg.exec_() if self.model_selection_dlg.model_is_loaded: simulation_template = self.model_selection_dlg.current_simulation_template simulation, settings_overview, events = self.get_simulation_data_from_template( simulation_template) self.simulation_init_wizard = SimulationInit(simulation_template, settings_overview, events, parent=self) self.simulation_init_wizard.exec_() if self.simulation_init_wizard.open_wizard: self.new_simulation(simulation, settings_overview, events) def get_simulation_data_from_template(self, template): """Fetching simulation, settings and events data from the simulation template.""" simulation, settings_overview, events = None, None, None try: tc = ThreediCalls(self.threedi_api) simulation = template.simulation settings_overview = tc.fetch_simulation_settings_overview( str(simulation.id)) events = tc.fetch_simulation_events(simulation.id) except ApiException as e: error_msg = extract_error_message(e) self.plugin_dock.communication.bar_error(error_msg) except Exception as e: error_msg = f"Error: {e}" self.plugin_dock.communication.bar_error(error_msg) return simulation, settings_overview, events def new_simulation(self, simulation, settings_overview, events): """Opening a wizard which allows defining and running new simulations.""" self.simulation_wizard = SimulationWizard(self.plugin_dock, self.model_selection_dlg, self.simulation_init_wizard) if simulation: self.simulation_wizard.load_template_parameters( simulation, settings_overview, events) self.close() self.simulation_wizard.exec_() new_simulations = self.simulation_wizard.new_simulations if new_simulations is not None: for sim in new_simulations: initial_status = self.simulation_wizard.new_simulation_statuses.get( sim.id) initial_progress = Progress(percentage=0, time=sim.duration) self.add_simulation_to_model(sim, initial_status, initial_progress) def stop_simulation(self): """Sending request to shut down currently selected simulation.""" index = self.tv_sim_tree.currentIndex() if not index.isValid(): return title = "Warning" question = "This simulation is now running.\nAre you sure you want to stop it?" answer = self.plugin_dock.communication.ask(self, title, question, QMessageBox.Warning) if answer is True: try: name_item = self.tv_model.item(index.row(), 0) sim_id = name_item.data(Qt.UserRole) tc = ThreediCalls(self.plugin_dock.threedi_api) tc.create_simulation_action(sim_id, name="shutdown") msg = f"Simulation {name_item.text()} stopped!" self.plugin_dock.communication.bar_info(msg) except ApiException as e: error_msg = extract_error_message(e) self.plugin_dock.communication.show_error(error_msg) except Exception as e: error_msg = f"Error: {e}" self.plugin_dock.communication.show_error(error_msg)
class ThinGreyscaleDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" self.iface = iface self.plugin_dir = dirname(__file__) self.THINGREYSCALE = self.tr('ThinGreyscale') self.BROWSE = self.tr('Browse') self.CANCEL = self.tr('Cancel') self.CLOSE = self.tr('Close') self.HELP = self.tr('Help') self.OK = self.tr('OK') self.DEFAULTPROVIDER = 'GTiff' self.DEFAULTEXTENSION = '.tif' self.EXTRAEXTENSION = ' *.tiff' super(ThinGreyscaleDialog, 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) self.showInfo("Connecting UI components") okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) cancelButton = self.button_box.button(QDialogButtonBox.Cancel) cancelButton.setText(self.CANCEL) cancelButton.setEnabled(False) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) browseButton = self.browseButton browseButton.setText(self.BROWSE) self.calcHistPushButton.setEnabled(False) self.listModel = QStandardItemModel(self.levelsListView) self.levelsListView.setModel(self.listModel) self.levelsListView.sizeHintForColumn(20) #self.levelValuesCheckBox.setEnabled(False) # Help button helpButton = self.helpButton helpButton.setText(self.HELP) # Connect signals self.showInfo("Connecting signals") okButton.clicked.connect(self.startWorker) cancelButton.clicked.connect(self.killWorker) closeButton.clicked.connect(self.reject) helpButton.clicked.connect(self.help) browseButton.clicked.connect(self.browse) inpIndexCh = self.inputRaster.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) bandCh = self.bandComboBox.currentIndexChanged['QString'] bandCh.connect(self.bandChanged) #self.iface.legendInterface().itemAdded.connect( # self.layerlistchanged) #self.iface.legendInterface().itemRemoved.connect( # self.layerlistchanged) #QObject.disconnect(self.button_box, SIGNAL("rejected()"), self.reject) self.button_box.rejected.disconnect(self.reject) calchistPr = self.calcHistPushButton.clicked calchistPr.connect(self.calculateHistogram) sugglevPr = self.suggestlevelsPushButton.clicked sugglevPr.connect(self.suggestLevels) addlevPr = self.addlevelPushButton.clicked addlevPr.connect(self.addLevel) dellevPr = self.deletelevelsPushButton.clicked dellevPr.connect(self.removeLevel) maxvalCh = self.maxValueSpinBox.valueChanged maxvalCh.connect(self.minmaxvalueChanged) maxvalFi = self.maxValueSpinBox.editingFinished maxvalFi.connect(self.minmaxvalueEdFinished) minvalCh = self.minValueSpinBox.valueChanged minvalCh.connect(self.minmaxvalueChanged) minvalFi = self.minValueSpinBox.editingFinished minvalFi.connect(self.minmaxvalueEdFinished) # Set instance variables #self.mem_layer = None self.worker = None self.inputlayerid = None self.inputlayer = None self.layerlistchanging = False self.minvalue = 1 self.inputrasterprovider = None self.histobins = 50 self.setupScene = QGraphicsScene(self) self.histoGraphicsView.setScene(self.setupScene) # Is the layer band of an integer type self.intband = False self.histogramAvailable = False self.histo = None self.histopadding = 1 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 workerFinished(self, ok, ret): """Handles the output from the worker and cleans up after the worker has finished.""" # clean up the worker and thread self.showInfo("Handling the result") self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() # remove widget from message bar (pop) self.iface.messageBar().popWidget(self.messageBar) if ok and ret is not None: #self.showInfo("Ret: "+str(ret[10,])) # Transformation: self.minx = self.thinninglayer.extent().xMinimum() self.maxx = self.thinninglayer.extent().xMaximum() self.miny = self.thinninglayer.extent().yMinimum() self.maxy = self.thinninglayer.extent().yMaximum() self.rows = self.thinninglayer.height() self.cols = self.thinninglayer.width() self.xres = (self.maxx - self.minx) / float(self.cols) self.yres = (self.maxy - self.miny) / float(self.rows) geotransform = (self.minx, self.xres, 0, self.maxy, 0, -self.yres) try: format = self.DEFAULTPROVIDER driver = gdal.GetDriverByName(format) NOVALUE = 0 metadata = driver.GetMetadata() fileName = self.outputRaster.text() if self.outputRaster.text() == "": self.showInfo("No output file specified, " + "creating a temporary file") # Get a temporary file fileName = mktemp(prefix='greyskel', suffix=self.DEFAULTEXTENSION) fileInfo = QFileInfo(fileName) filepath = fileInfo.absolutePath() baseName = fileInfo.baseName() suffix = fileInfo.suffix() thisfilename = filepath + baseName + '.' + suffix thisfilename = fileName self.showInfo("File name: " + thisfilename) gdaldatatype = gdal.GDT_Byte skelmatrix = None if self.levelValuesCheckBox.isChecked(): # Transform the pixel values back to the original # level values my_dict = {} # Add zero to handle the "empty" pixels my_dict[0] = 0 for i in range(len(self.levels)): my_dict[i + 1] = self.levels[i] skelmatrix = np.vectorize(my_dict.__getitem__, otypes=[np.float])(ret) gdaldatatype = gdal.GDT_Int32 if not self.intband: gdaldatatype = gdal.GDT_Float32 else: skelmatrix = ret outDataset = driver.Create(thisfilename, self.cols, self.rows, 1, gdaldatatype) if self.thinninglayer.dataProvider().crs() is not None: srs = self.thinninglayer.dataProvider().crs() outDataset.SetProjection(srs.toWkt().encode( 'ascii', 'ignore')) skeletonband = outDataset.GetRasterBand(1) skeletonband.WriteArray(skelmatrix) skeletonband.SetNoDataValue(NOVALUE) #stats = skeletonband.GetStatistics(False, True) #skeletonband.SetStatistics(stats[0], stats[1], # stats[2], stats[3]) outDataset.SetGeoTransform(geotransform) outDataset = None # To close the file # report the result rlayer = QgsRasterLayer(thisfilename, baseName) self.layerlistchanging = True #QgsMapLayerRegistry.instance().addMapLayer(rlayer) QgsProject.instance().addMapLayer(rlayer) self.layerlistchanging = False except: import traceback self.showError("Can't write the skeleton file: %s" % self.outputRaster.text() + ' - ' + traceback.format_exc()) okb = self.button_box.button(QDialogButtonBox.Ok) okb.setEnabled(True) closb = self.button_box.button(QDialogButtonBox.Close) closb.setEnabled(True) cancb = self.button_box.button(QDialogButtonBox.Cancel) cancb.setEnabled(False) return QgsMessageLog.logMessage(self.tr('ThinGreyscale finished'), self.THINGREYSCALE, Qgis.Info) else: # notify the user that something went wrong if not ok: self.showError(self.tr('Aborted') + '!') else: self.showError(self.tr('No skeleton created') + '!') self.progressBar.setValue(0.0) #self.aprogressBar.setValue(0.0) self.iterProgressBar.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) def workerError(self, exception_string): """Report an error from the worker.""" #QgsMessageLog.logMessage(self.tr('Worker failed - exception') + # ': ' + str(exception_string), # self.THINGREYSCALE, # QgsMessageLog.CRITICAL) self.showError(exception_string) def workerInfo(self, message_string): """Report an info message from the worker.""" QgsMessageLog.logMessage( self.tr('Worker') + ': ' + message_string, self.THINGREYSCALE, Qgis.Info) def layerchanged(self, number=0): """Do the necessary updates after a layer selection has been changed.""" self.showInfo("Layer changed") # If the layer list is being updated, don't do anything if self.layerlistchanging: return layerindex = self.inputRaster.currentIndex() layerId = self.inputRaster.itemData(layerindex) self.inputlayerid = layerId #self.inputlayer = QgsMapLayerRegistry.instance().mapLayer(layerId) self.inputlayer = QgsProject.instance().mapLayer(layerId) if self.inputlayer is not None: self.inputrasterprovider = self.inputlayer.dataProvider() self.bandComboBox.clear() bandcount = self.inputlayer.bandCount() #self.showInfo("Layer bandcount: "+str(bandcount)) for i in range(bandcount): self.bandComboBox.addItem(self.inputlayer.bandName(i + 1), i) #self.showInfo("Band " + str(i) + ": " + # self.inputlayer.bandName(i+1)) # Check if the driver supports Create() or CreateCopy() #gdalmetadata = self.inputlayer.metadata() #self.showInfo("Layer metadata: " + # str(gdalmetadata.encode('utf-8'))) #provstring = '<p>GDAL provider</p>\n' #providerpos = gdalmetadata.find(provstring) #brpos = gdalmetadata.find('<br>', providerpos + len(provstring)) #self.gdalprovider = gdalmetadata[int(providerpos + # len(provstring)):int(brpos)] #self.showInfo('GDAL provider: '+self.gdalprovider) #drivername = self.gdalprovider.encode('ascii', 'ignore') #theDriver = gdal.GetDriverByName(drivername) #if theDriver is None: # self.showInfo("Unable to get the raster driver") #else: #data theMetadata = theDriver.GetMetadata() #self.showInfo("Driver metadata: "+str(theMetadata)) #if ((gdal.DCAP_CREATE in theMetadata) and # theMetadata[gdal.DCAP_CREATE] == 'YES'): # self.canCreate = True #if (theMetadata.has_key(gdal.DCAP_CREATECOPY) and #if ((gdal.DCAP_CREATECOPY in theMetadata) and # theMetadata[gdal.DCAP_CREATECOPY] == 'YES'): # self.canCreateCopy = True #self.showInfo('raster provider type: ' + # str(self.inputlayer.providerType())) # Determine the file suffix #self.gdalext = "" #if gdal.DMD_EXTENSION in theMetadata: # self.gdalext = "." + theMetadata[gdal.DMD_EXTENSION] #else: # self.showInfo("No extension available in GDAL metadata") # by parsing the layer metadata looking for # "Dataset Description" #descstring = 'Dataset Description</p>\n<p>' #descpos = gdalmetadata.find(descstring) #ppos = gdalmetadata.find('</p>',descpos+len(descstring)) #filename = gdalmetadata[descpos+len(descstring):ppos] #self.gdalext = splitext(filename)[1] #self.showInfo('GDAL extension: '+self.gdalext) # Determine the datatype #datatypestring = 'Data Type</p>\n<p>' #datatypepos = gdalmetadata.find(datatypestring) #ppos = gdalmetadata.find('</p>', # datatypepos + len(datatypestring)) #datatypedesc = gdalmetadata[datatypepos + # len(datatypestring):ppos] #shortdesc = datatypedesc.split()[0] #self.showInfo('GDAL data type: GDT_'+shortdesc) # Call the findGdalDatatype function #self.findGdalDatatype(shortdesc) # self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.calcHistPushButton.setEnabled(True) self.suggestlevelsPushButton.setEnabled(True) def bandChanged(self): band = self.bandComboBox.currentIndex() + 1 self.showInfo("Band changed: " + str(band)) statistics = self.inputrasterprovider.bandStatistics(band) #self.showInfo("Band statistics: " + str(statistics.minimumValue) + # " - " + str(statistics.maximumValue) + # " - " + str(statistics.mean)) self.bandmin = statistics.minimumValue self.bandmax = statistics.maximumValue dt = self.inputrasterprovider.dataType(band) # Integer data type if (dt == Qgis.Byte or dt == Qgis.UInt16 or dt == Qgis.Int16 or dt == Qgis.UInt32 or dt == Qgis.Int32): self.intband = True self.minValueSpinBox.setDecimals(0) self.maxValueSpinBox.setDecimals(0) self.levelSpinBox.setDecimals(0) self.bandMinLabel.setText(str(int(statistics.minimumValue))) self.bandMaxLabel.setText(str(int(statistics.maximumValue))) else: self.intband = False self.minValueSpinBox.setDecimals(5) self.maxValueSpinBox.setDecimals(5) self.levelSpinBox.setDecimals(5) minlabtext = "{0:.5f}".format(statistics.minimumValue) self.bandMinLabel.setText(minlabtext) maxlabtext = "{0:.5f}".format(statistics.maximumValue) self.bandMaxLabel.setText(maxlabtext) #self.minValueSpinBox.setMinimum(statistics.minimumValue) self.maxValueSpinBox.setMinimum(statistics.minimumValue) #self.minValueSpinBox.setMaximum(statistics.maximumValue) self.maxValueSpinBox.setMaximum(statistics.maximumValue) #self.minValueSpinBox.setValue(statistics.minimumValue) if not (statistics.statsGathered & statistics.Mean): bandmean = (statistics.minimumValue + statistics.maximumValue) / 2 else: #self.showInfo("statsgathered: " + str(statistics.statsGathered)) bandmean = statistics.mean if self.intband: self.minValueSpinBox.setValue(int(ceil(bandmean))) else: self.minValueSpinBox.setValue(bandmean) self.maxValueSpinBox.setValue(statistics.maximumValue) self.histMinValue.setText(str(statistics.minimumValue)) self.histMaxValue.setText(str(statistics.maximumValue)) self.levelSpinBox.setMinimum(statistics.minimumValue) self.levelSpinBox.setMaximum(statistics.maximumValue) self.histogramAvailable = False #if self.inputrasterprovider.hasStatistics(band): #if statistics.statsGathered: #histogram = statistics.histogramVector #self.showInfo("Histogram: " + str(histogram)) #range = min to max #np.histogram(band, 50, range) def minmaxvalueChanged(self): #if self.minValueSpinBox is None: # return minvalue = self.minValueSpinBox.value() #if minvalue is None: # return #if self.maxValueSpinBox is None: # return maxvalue = self.maxValueSpinBox.value() #if maxvalue is None: # return if isnan(maxvalue) or isnan(minvalue): return self.showInfo("minvalue: " + str(minvalue) + " Maxvalue: " + str(maxvalue)) #if self.intband: # minvalue = int(minvalue) # maxvalue = int(maxvalue) if abs(maxvalue - minvalue) < 0.00001: #if maxvalue == maxvalue: self.calcHistPushButton.setEnabled(False) else: self.calcHistPushButton.setEnabled(True) # Update the min and max value spinboxes self.minValueSpinBox.setMaximum(maxvalue) self.maxValueSpinBox.setMinimum(minvalue) self.minValueSpinBox.setMinimum(self.bandmin) def minmaxvalueEdFinished(self): minvalue = self.minValueSpinBox.value() maxvalue = self.maxValueSpinBox.value() if self.intband: minvalue = int(minvalue) maxvalue = int(maxvalue) self.showInfo("minvalue: " + str(minvalue) + " Maxvalue: " + str(maxvalue)) # Update the spin box for adding levels self.levelSpinBox.setMinimum(minvalue) self.levelSpinBox.setMaximum(maxvalue) if self.levelSpinBox.value() < minvalue: self.levelSpinBox.setValue(minvalue) if self.levelSpinBox.value() > maxvalue: self.levelSpinBox.setValue(maxvalue) # Update the min and max value spinboxes self.minValueSpinBox.setMaximum(maxvalue) self.maxValueSpinBox.setMinimum(minvalue) # Adjust the levels: i = 0 while self.levelsListView.model().item(i): #for i in range(self.levelsListView.model().rowCount()): #self.showInfo("Element: " + # str(self.levelsListView.model().item(i).text())) #continue value = float(self.levelsListView.model().item(i).text()) if value < minvalue: if i == 0: self.levelsListView.model().item(i).setText(str(minvalue)) i = i + 1 else: self.levelsListView.model().removeRow(i) elif value > maxvalue: if i == self.levelsListView.model().rowCount() - 1: self.levelsListView.model().item(i).setText(str(maxvalue)) i = i + 1 else: self.levelsListView.model().removeRow(i) else: i = i + 1 self.drawHistogram() def calculateHistogram(self): self.showInfo("Calculating histogram...") if self.inputlayer is None: return self.showInfo("Calculating histogram...") # Check if there is only one value myrange = (self.minValueSpinBox.value(), self.maxValueSpinBox.value()) self.inputextent = self.inputlayer.extent() self.inputrdp = self.inputlayer.dataProvider() width = self.inputlayer.width() height = self.inputlayer.height() if width == 0 or height == 0: self.showInfo("Image has zero width or height") return extwidth = self.inputextent.width() extheight = self.inputextent.height() # Read the raster block and get the maximum value rasterblock = self.inputrdp.block(1, self.inputextent, width, height) # Create a numpy array version of the image imageMat = np.zeros((height, width), dtype=np.float16) # This one takes a lot of time! for row in range(height): for column in range(width): imageMat[row, column] = rasterblock.value(row, column) self.showInfo("Image: " + str(height) + ", " + str(width) + " - " + str(imageMat[row, column])) self.histo = np.histogram(imageMat, self.histobins, myrange) #relevantpixels = imageMat[np.where(imageMat >= bandval)] minlevel = float(self.bandMinLabel.text()) relevantpixels = imageMat[np.where(imageMat >= minlevel)] #self.showInfo("Histogram: " + str(self.histo)) nanpercentage = 100.0 - 100.0 * len(relevantpixels) / (width * height) self.bandNANLabel.setText("{0:.1f}".format(nanpercentage)) #self.showInfo("Percentage NAN: " + str(100.0 - 100.0 * # len(relevantpixels) / (width * height))) #self.showInfo("First element: " + str(self.histo[0])) #self.showInfo("First element, first: " + str(self.histo[0][0])) #self.showInfo("First element, second: " + str(self.histo[0][1])) self.histMinValue.setText(str(self.minValueSpinBox.value())) self.histMaxValue.setText(str(self.maxValueSpinBox.value())) if self.intband: self.histMinValue.setText(str(int(self.minValueSpinBox.value()))) self.histMaxValue.setText(str(int(self.maxValueSpinBox.value()))) self.histogramAvailable = True self.drawHistogram() def drawHistogram(self): #if self.inputlayer is None: # return self.showInfo("Drawing histogram...") viewprect = QRectF(self.histoGraphicsView.viewport().rect()) self.histoGraphicsView.setSceneRect(viewprect) self.setupScene.clear() self.setupScene.update() histbottom = self.histoGraphicsView.sceneRect().bottom() histtop = self.histoGraphicsView.sceneRect().top() left = self.histoGraphicsView.sceneRect().left() + self.histopadding right = self.histoGraphicsView.sceneRect().right() - self.histopadding histheight = histbottom - histtop histwidth = right - left step = 1.0 * histwidth / self.histobins maxlength = histheight padding = 1 ll = QPoint(self.histopadding - 1, histheight - padding) start = QPointF(self.histoGraphicsView.mapToScene(ll)) # Check if there is only one value #myrange = (self.minValueSpinBox.value(),self.maxValueSpinBox.value()) if self.histogramAvailable: maxvalue = 0.0 for i in range(len(self.histo[0])): if self.histo[0][i] > maxvalue: maxvalue = self.histo[0][i] if maxvalue == 0: return self.maxBinNumber.setText(str(maxvalue)) # Create the histogram: #self.showInfo("maxvalue: " + str(maxvalue)) #self.showInfo("maxlength: " + str(maxlength)) #self.showInfo("step: " + str(step)) for i in range(self.histobins): binnumber = self.histo[0][i] if binnumber == 0: continue height = (1.0 * self.histo[0][i] / maxvalue * (maxlength - padding)) rectangle = QGraphicsRectItem(start.x() + step * i, start.y(), step, -height) rectangle.setPen(QPen(QColor(102, 102, 102))) rectangle.setBrush(QBrush(QColor(240, 240, 240))) self.setupScene.addItem(rectangle) #self.showInfo(str(i) + ": " + str(height)) #if self.levelsListView.model().rowCount() > 0: # Add lines for the levels minvalue = float(self.histMinValue.text()) maxvalue = float(self.histMaxValue.text()) datarange = maxvalue - minvalue if datarange == 0: return i = 0 while self.levelsListView.model().item(i): #self.showInfo("Element: " + # str(self.levelsListView.model().item(i).text())) #continue value = float(self.levelsListView.model().item(i).text()) xvalue = start.x() + histwidth * (value - minvalue) / datarange line = QGraphicsLineItem(xvalue, 0, xvalue, histheight) if i == 0 or i == (self.levelsListView.model().rowCount() - 1): line.setPen(QPen(QColor(204, 0, 0))) else: line.setPen(QPen(QColor(0, 204, 0))) self.setupScene.addItem(line) i = i + 1 def suggestLevels(self): self.listModel.clear() self.showInfo("Suggesting levels") levels = self.levelsSpinBox.value() startvalue = self.minValueSpinBox.value() endvalue = self.maxValueSpinBox.value() increment = (endvalue - startvalue) / levels for i in range(levels + 1): value = startvalue + increment * i if self.intband: value = int(value) item = QStandardItem(str(value)) self.listModel.appendRow(item) self.drawHistogram() def addLevel(self): newvalue = self.levelSpinBox.value() if self.intband: newvalue = int(newvalue) for i in range(self.listModel.rowCount()): # Check if the value is already in the list if self.listModel.item(i).text() == str(newvalue): return else: # Maintain a sorted list of distances if (float(self.listModel.item(i).text()) > float( str(newvalue))): item = QStandardItem(str(newvalue)) self.listModel.insertRow(i, item) self.drawHistogram() return item = QStandardItem(str(newvalue)) self.listModel.appendRow(item) #if self.histogramAvailable: # addLevelsToHistogram() self.drawHistogram() def removeLevel(self): self.levelsListView.setUpdatesEnabled(False) indexes = self.levelsListView.selectedIndexes() indexes.sort() for i in range(len(indexes) - 1, -1, -1): self.listModel.removeRow(indexes[i].row()) self.levelsListView.setUpdatesEnabled(True) #if self.histogramAvailable: # removeLevelFromHistogram() self.drawHistogram() def layerlistchanged(self): self.layerlistchanging = True self.showInfo("Layer list changed") # Repopulate the input layer combo box # Save the currently selected input layer inputlayerid = self.inputlayerid self.inputRaster.clear() for alayer in self.iface.legendInterface().layers(): if alayer.type() == QgsMapLayer.RasterLayer: gdalmetadata = alayer.metadata() # Skip WMS layers WMSstring = 'Web Map Service' wmspos = gdalmetadata.find(WMSstring) if wmspos != -1: continue self.inputRaster.addItem(alayer.name(), alayer.id()) # Set the previous selection for i in range(self.inputRaster.count()): if self.inputRaster.itemData(i) == inputlayerid: self.inputRaster.setCurrentIndex(i) self.layerlistchanging = False #self.updateui() def updateui(self): """Do the necessary updates after a layer selection has been changed.""" #if self.layerlistchanged: # return #self.outputRaster.setText(self.inputRaster.currentText() + # '_' + 'thinned') layerindex = self.inputRaster.currentIndex() layerId = self.inputRaster.itemData(layerindex) #inputlayer = QgsMapLayerRegistry.instance().mapLayer(layerId) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is not None: pass else: pass def findGdalDatatype(self, shortdesc): gdaldatatype = None # // Unknown or unspecified type # GDT_Unknown = GDALDataType(C.GDT_Unknown) if shortdesc == 'Unknown': gdaldatatype = gdal.GDT_Unknown # // Eight bit unsigned integer # GDT_Byte = GDALDataType(C.GDT_Byte) elif shortdesc == 'Byte': gdaldatatype = gdal.GDT_Byte # // Sixteen bit unsigned integer # GDT_UInt16 = GDALDataType(C.GDT_UInt16) elif shortdesc == 'UInt16': gdaldatatype = gdal.GDT_UInt16 # // Sixteen bit signed integer # GDT_Int16 = GDALDataType(C.GDT_Int16) elif shortdesc == 'Int16': gdaldatatype = gdal.GDT_Int16 # // Thirty two bit unsigned integer # GDT_UInt32 = GDALDataType(C.GDT_UInt32) elif shortdesc == 'UInt32': gdaldatatype = gdal.GDT_UInt32 # // Thirty two bit signed integer # GDT_Int32 = GDALDataType(C.GDT_Int32) elif shortdesc == 'Int32': gdaldatatype = gdal.GDT_Int32 # // Thirty two bit floating point # GDT_Float32 = GDALDataType(C.GDT_Float32) elif shortdesc == 'Float32': gdaldatatype = gdal.GDT_Float32 # // Sixty four bit floating point # GDT_Float64 = GDALDataType(C.GDT_Float64) elif shortdesc == 'Float64': gdaldatatype = gdal.GDT_Float64 # // Complex Int16 # GDT_CInt16 = GDALDataType(C.GDT_CInt16) elif shortdesc == 'CInt16': gdaldatatype = gdal.CInt16 # // Complex Int32 # GDT_CInt32 = GDALDataType(C.GDT_CInt32) elif shortdesc == 'CInt32': gdaldatatype = gdal.CInt32 # // Complex Float32 # GDT_CFloat32 = GDALDataType(C.GDT_CFloat32) elif shortdesc == 'CFloat32': gdaldatatype = gdal.CFloat32 # // Complex Float64 # GDT_CFloat64 = GDALDataType(C.GDT_CFloat64) elif shortdesc == 'CFloat64': gdaldatatype = gdal.CFloat64 # // maximum type # + 1 # GDT_TypeCount = GDALDataType(C.GDT_TypeCount) elif shortdesc == 'TypeCount': gdaldatatype = gdal.TypeCount self.gdaldatatype = gdaldatatype def killWorker(self): """Kill the worker thread.""" if self.worker is not None: QgsMessageLog.logMessage(self.tr('Killing worker'), self.THINGREYSCALE, Qgis.Info) self.worker.kill() def showError(self, text): """Show an error.""" self.iface.messageBar().pushMessage(self.tr('Error'), text, level=QgsMessageBar.CRITICAL, duration=3) QgsMessageLog.logMessage('Error: ' + text, self.THINGREYSCALE, QgsMessageLog.CRITICAL) def showWarning(self, text): """Show a warning.""" self.iface.messageBar().pushMessage(self.tr('Warning'), text, level=QgsMessageBar.WARNING, duration=2) QgsMessageLog.logMessage('Warning: ' + text, self.THINGREYSCALE, QgsMessageLog.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.THINGREYSCALE, Qgis.Info) # def help(self): # #QDesktopServices.openUrl(QUrl.fromLocalFile(self.plugin_dir + # "/help/build/html/index.html")) # QDesktopServices.openUrl(QUrl.fromLocalFile(self.plugin_dir + # "/help/index.html")) # #showPluginHelp() 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('ThinGreyScaleDialog', message) def browse(self): settings = QSettings() key = '/UI/lastShapefileDir' outDir = settings.value(key) home = outDir #outDir = expanduser("~") #filter = (self.DEFAULTPROVIDER + " (*" + # self.DEFAULTEXTENSION + ");;All files (*)") filter = (self.DEFAULTPROVIDER + " (*" + self.DEFAULTEXTENSION + self.EXTRAEXTENSION + ")") #if (self.gdalprovider != self.DEFAULTPROVIDER and # (self.canCreateCopy or # self.canCreate)): # filter = (self.gdalprovider + " (*" + self.gdalext + # ");;" + filter) outFilePath = QFileDialog.getSaveFileName( self, 'Specify file name for skeleton', outDir, filter) outFilePath = unicode(outFilePath) if outFilePath: root, ext = splitext(outFilePath) if ext.lower() != '.tif' and ext.lower() != '.tiff': outFilePath = '%s.tif' % outFilePath outDir = dirname(outFilePath) settings.setValue(key, outDir) # (self.canCreateCopy or self.canCreate): # fileName = splitext(str(fileName))[0]+self.gdalext self.outputRaster.setText(outFilePath) # Overriding def resizeEvent(self, event): #self.showInfo("resizeEvent") self.calculateHistogram() def help(self): #QDesktopServices.openUrl(QUrl.fromLocalFile( # self.plugin_dir + "/help/html/index.html")) showPluginHelp(None, "help/html/index") # 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 MultipleInputDialog(BASE, WIDGET): def __init__(self, options, selectedoptions=None, datatype=None): super(MultipleInputDialog, self).__init__(None) self.setupUi(self) self.datatype = datatype self.model = None self.options = [] for i, option in enumerate(options): if option is None or isinstance(option, str): self.options.append((i, option)) else: self.options.append((option[0], option[1])) self.selectedoptions = selectedoptions or [] # Additional buttons self.btnSelectAll = QPushButton(self.tr('Select All')) self.buttonBox.addButton(self.btnSelectAll, QDialogButtonBox.ActionRole) self.btnClearSelection = QPushButton(self.tr('Clear Selection')) self.buttonBox.addButton(self.btnClearSelection, QDialogButtonBox.ActionRole) self.btnToggleSelection = QPushButton(self.tr('Toggle Selection')) self.buttonBox.addButton(self.btnToggleSelection, QDialogButtonBox.ActionRole) if self.datatype is not None: btnAddFile = QPushButton(QCoreApplication.translate("MultipleInputDialog", 'Add File(s)…')) btnAddFile.clicked.connect(self.addFiles) self.buttonBox.addButton(btnAddFile, QDialogButtonBox.ActionRole) self.btnSelectAll.clicked.connect(lambda: self.selectAll(True)) self.btnClearSelection.clicked.connect(lambda: self.selectAll(False)) self.btnToggleSelection.clicked.connect(self.toggleSelection) self.settings = QgsSettings() self.restoreGeometry(self.settings.value("/Processing/multipleInputDialogGeometry", QByteArray())) self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection) self.lstLayers.setDragDropMode(QAbstractItemView.InternalMove) self.populateList() self.finished.connect(self.saveWindowGeometry) def saveWindowGeometry(self): self.settings.setValue("/Processing/multipleInputDialogGeometry", self.saveGeometry()) def populateList(self): self.model = QStandardItemModel() for value, text in self.options: item = QStandardItem(text) item.setData(value, Qt.UserRole) item.setCheckState(Qt.Checked if value in self.selectedoptions else Qt.Unchecked) item.setCheckable(True) item.setDropEnabled(False) self.model.appendRow(item) # add extra options (e.g. manually added layers) for t in [o for o in self.selectedoptions if not isinstance(o, int)]: if isinstance(t, QgsProcessingModelChildParameterSource): item = QStandardItem(t.staticValue()) else: item = QStandardItem(t) item.setData(item.text(), Qt.UserRole) item.setCheckState(Qt.Checked) item.setCheckable(True) item.setDropEnabled(False) self.model.appendRow(item) self.lstLayers.setModel(self.model) def accept(self): self.selectedoptions = [] model = self.lstLayers.model() for i in range(model.rowCount()): item = model.item(i) if item.checkState() == Qt.Checked: self.selectedoptions.append(item.data(Qt.UserRole)) QDialog.accept(self) def reject(self): self.selectedoptions = None QDialog.reject(self) def getItemsToModify(self): items = [] if len(self.lstLayers.selectedIndexes()) > 1: for i in self.lstLayers.selectedIndexes(): items.append(self.model.itemFromIndex(i)) else: for i in range(self.model.rowCount()): items.append(self.model.item(i)) return items def selectAll(self, value): for item in self.getItemsToModify(): item.setCheckState(Qt.Checked if value else Qt.Unchecked) def toggleSelection(self): for item in self.getItemsToModify(): checked = item.checkState() == Qt.Checked item.setCheckState(Qt.Unchecked if checked else Qt.Checked) def getFileFilter(self, datatype): """ Returns a suitable file filter pattern for the specified parameter definition :param param: :return: """ if datatype == QgsProcessing.TypeRaster: return QgsProviderRegistry.instance().fileRasterFilters() elif datatype == QgsProcessing.TypeFile: return self.tr('All files (*.*)') else: exts = QgsVectorFileWriter.supportedFormatExtensions() for i in range(len(exts)): exts[i] = self.tr('{0} files (*.{1})').format(exts[i].upper(), exts[i].lower()) return self.tr('All files (*.*)') + ';;' + ';;'.join(exts) def addFiles(self): filter = self.getFileFilter(self.datatype) settings = QgsSettings() path = str(settings.value('/Processing/LastInputPath')) ret, selected_filter = QFileDialog.getOpenFileNames(self, self.tr('Select File(s)'), path, filter) if ret: files = list(ret) settings.setValue('/Processing/LastInputPath', os.path.dirname(str(files[0]))) for filename in files: item = QStandardItem(filename) item.setData(filename, Qt.UserRole) item.setCheckState(Qt.Checked) item.setCheckable(True) item.setDropEnabled(False) self.model.appendRow(item)
class QRAVEDockWidget(QDockWidget, Ui_QRAVEDockWidgetBase): closingPlugin = pyqtSignal() dataChange = pyqtSignal() showMeta = pyqtSignal() metaChange = pyqtSignal(str, str, dict, bool) def __init__(self, parent=None): """Constructor.""" super(QRAVEDockWidget, self).__init__(parent) self.setupUi(self) self.menu = ContextMenu() self.qproject = QgsProject.instance() self.qproject.cleared.connect(self.close_all) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect(self.open_menu) self.treeView.doubleClicked.connect(self.default_tree_action) self.treeView.clicked.connect(self.item_change) self.treeView.expanded.connect(self.expand_tree_item) self.settings = Settings() self.model = QStandardItemModel() self.loaded_projects: List(Project) = [] # Initialize our classes self.basemaps = BaseMaps() self.treeView.setModel(self.model) self.dataChange.connect(self.reload_tree) self.reload_tree() def expand_tree_item(self, idx: QModelIndex): item = self.model.itemFromIndex(idx) item_data = item.data(Qt.UserRole) if item_data and item_data.data and isinstance(item_data.data, QRaveBaseMap): item_data.data.load_layers() def _get_project(self, xml_path): try: return next(iter(self.loaded_projects)) except Exception: return None @pyqtSlot() def reload_tree(self): # re-initialize our model and reload the projects from file # Try not to do this too often if you can self.model.clear() self.loaded_projects = [] qrave_projects = self.get_project_settings() for project_path in qrave_projects: project = Project(project_path) project.load() if project is not None and project.exists is True and project.qproject is not None: self.model.appendRow(project.qproject) self.expand_children_recursive(self.model.indexFromItem(project.qproject)) self.loaded_projects.append(project) # Load the tree objects self.basemaps.load() # Now load the basemaps region = self.settings.getValue('basemapRegion') if self.settings.getValue('basemapsInclude') is True \ and region is not None and len(region) > 0 \ and region in self.basemaps.regions.keys(): self.model.appendRow(self.basemaps.regions[region]) self.expand_children_recursive(self.model.indexFromItem(self.basemaps.regions[region])) def get_project_settings(self): try: qrave_projects_raw, type_conversion_ok = self.qproject.readEntry( CONSTANTS['settingsCategory'], 'qrave_projects' ) if type_conversion_ok is False: qrave_projects = [] else: qrave_projects = json.loads(qrave_projects_raw) if qrave_projects is None or not isinstance(qrave_projects, list): qrave_projects = [] except Exception as e: self.settings.log('Error loading project settings: {}'.format(e), Qgis.Warning) qrave_projects = [] filtered = [pf for pf in qrave_projects if os.path.isfile(pf)] filtered.reverse() # We Treat this like a stack where the last project in goes on the top. # Element 0 should be the top item return filtered def set_project_settings(self, projects: List[str]): self.qproject.writeEntry(CONSTANTS['settingsCategory'], 'qrave_projects', json.dumps(projects)) @pyqtSlot() def add_project(self, xml_path: str): qrave_projects = self.get_project_settings() # If this project is not already in if xml_path not in qrave_projects: qrave_projects.append(xml_path) self.set_project_settings(qrave_projects) self.reload_tree() new_project = self._get_project(xml_path) # If this is a fresh load and the setting is set we load the default view load_default_setting = self.settings.getValue('loadDefaultView') if load_default_setting is True \ and new_project.default_view is not None \ and new_project.default_view in new_project.views: self.add_children_to_map(new_project.qproject, new_project.views[new_project.default_view]) def closeEvent(self, event): """ When the user clicks the "X" in the dockwidget titlebar """ self.hide() self.qproject.removeEntry(CONSTANTS['settingsCategory'], 'enabled') self.closingPlugin.emit() event.accept() def expand_children_recursive(self, idx: QModelIndex = None, force=False): """Expand all the children of a QTreeView node. Do it recursively TODO: Recursion might not be the best for large trees here. Args: idx (QModelIndex, optional): [description]. Defaults to None. force: ignore the "collapsed" business logic attribute """ if idx is None: idx = self.treeView.rootIndex() for idy in range(self.model.rowCount(idx)): child = self.model.index(idy, 0, idx) self.expand_children_recursive(child, force) item = self.model.itemFromIndex(idx) item_data = item.data(Qt.UserRole) if item is not None else None # NOTE: This is pretty verbose on purpose # This thing needs to have data or it defaults to being expanded if item_data is None or item_data.data is None: collapsed = False # Collapsed is an attribute set in the business logic # Never expand the QRaveBaseMap object becsause there's a network call involved elif isinstance(item_data.data, QRaveBaseMap) \ or (isinstance(item_data.data, dict) and 'collapsed' in item_data.data and item_data.data['collapsed'] == 'true'): collapsed = True else: collapsed = False if not self.treeView.isExpanded(idx) and not collapsed: self.treeView.setExpanded(idx, True) def default_tree_action(self, idx: QModelIndex): if not idx.isValid(): return item = self.model.itemFromIndex(idx) item_data: ProjectTreeData = item.data(Qt.UserRole) # This is the default action for all add-able layers including basemaps if isinstance(item_data.data, QRaveMapLayer): if item_data.data.layer_type in [QRaveMapLayer.LayerTypes.FILE, QRaveMapLayer.LayerTypes.REPORT]: self.file_system_open(item_data.data.layer_uri) else: QRaveMapLayer.add_layer_to_map(item) # Expand is the default option for wms because we might need to load the layers elif isinstance(item_data.data, QRaveBaseMap): if item_data.data.tile_type == 'wms': pass # All the XYZ layers can be added normally. else: QRaveMapLayer.add_layer_to_map(item) elif item_data.type in [QRaveTreeTypes.PROJECT_ROOT]: self.change_meta(item, item_data, True) # For folder-y types we want Expand and contract is already implemented as a default elif item_data.type in [ QRaveTreeTypes.PROJECT_FOLDER, QRaveTreeTypes.PROJECT_REPEATER_FOLDER, QRaveTreeTypes.PROJECT_VIEW_FOLDER, QRaveTreeTypes.BASEMAP_ROOT, QRaveTreeTypes.BASEMAP_SUPER_FOLDER, QRaveTreeTypes.BASEMAP_SUB_FOLDER ]: # print("Default Folder Action") pass elif item_data.type == QRaveTreeTypes.PROJECT_VIEW: print("Default View Action") self.add_view_to_map(item_data) def item_change(self, pos): """Triggered when the user selects a new item in the tree Args:pos pos ([type]): [description] """ indexes = self.treeView.selectedIndexes() # No multiselect so there is only ever one item item = self.model.itemFromIndex(indexes[0]) data_item: ProjectTreeData = item.data(Qt.UserRole) if len(indexes) < 1 or data_item.project is None or not data_item.project.exists: return # Update the metadata if we need to self.change_meta(item, data_item) def change_meta(self, item: QStandardItem, item_data: ProjectTreeData, show=False): """Update the MetaData dock widget with new information Args: item (QStandardItem): [description] data ([type]): [description] show (bool, optional): [description]. Defaults to False. """ data = item_data.data if isinstance(data, QRaveMapLayer): meta = data.meta if data.meta is not None else {} self.metaChange.emit(item.text(), MetaType.LAYER, meta, show) elif isinstance(data, QRaveBaseMap): self.metaChange.emit(item.text(), MetaType.NONE, {}, show) elif item_data.type == QRaveTreeTypes.PROJECT_ROOT: self.metaChange.emit(item.text(), MetaType.PROJECT, { 'project': item_data.project.meta, 'warehouse': item_data.project.warehouse_meta }, show) elif item_data.type in [ QRaveTreeTypes.PROJECT_FOLDER, QRaveTreeTypes.PROJECT_REPEATER_FOLDER, QRaveTreeTypes.PROJECT_VIEW_FOLDER, QRaveTreeTypes.BASEMAP_ROOT, QRaveTreeTypes.BASEMAP_SUPER_FOLDER, QRaveTreeTypes.BASEMAP_SUB_FOLDER ]: self.metaChange.emit(item.text(), MetaType.FOLDER, data or {}, show) elif isinstance(data, dict): # this is just the generic case for any kind of metadata self.metaChange.emit(item.text(), MetaType.NONE, data or {}, show) else: # Do not update the metadata if we have nothing to show self.metaChange.emit(item.text(), MetaType.NONE, {}, show) def get_warehouse_url(self, wh_meta: Dict[str, str]): if wh_meta is not None: if 'program' in wh_meta and 'id' in wh_meta: return '/'.join([CONSTANTS['warehouseUrl'], wh_meta['program'], wh_meta['id']]) elif '_rs_wh_id' in wh_meta and '_rs_wh_program' in wh_meta: return '/'.join([CONSTANTS['warehouseUrl'], wh_meta['_rs_wh_program'], wh_meta['_rs_wh_id']]) return None def project_warehouse_view(self, project: Project): """Open this project in the warehouse if the warehouse meta entries exist """ url = self.get_warehouse_url(project.warehouse_meta) if url is not None: QDesktopServices.openUrl(QUrl(url)) def layer_warehouse_view(self, data: QRaveMapLayer): """Open this project in the warehouse if the warehouse meta entries exist """ url = self.get_warehouse_url(data.meta) if url is not None: QDesktopServices.openUrl(QUrl(url)) def close_all(self): for p in range(len(self.loaded_projects)): self.close_project(self.loaded_projects[0]) def close_project(self, project: Project): """ Close the project """ try: qrave_projects_raw, type_conversion_ok = self.qproject.readEntry( CONSTANTS['settingsCategory'], 'qrave_projects' ) qrave_projects = json.loads(qrave_projects_raw) if not type_conversion_ok or qrave_projects is None: qrave_projects = [] except Exception as e: self.settings.log('Error closing project: {}'.format(e), Qgis.Warning) qrave_projects = [] # Filter out the project we want to close and reload the tree qrave_projects = [x.project_xml_path for x in self.loaded_projects if x != project] # Write the settings back to the project self.qproject.writeEntry(CONSTANTS['settingsCategory'], 'qrave_projects', json.dumps(qrave_projects)) self.loaded_projects = qrave_projects self.reload_tree() def file_system_open(self, fpath: str): """Open a file on the operating system using the default action Args: fpath (str): [description] """ qurl = QUrl.fromLocalFile(fpath) QDesktopServices.openUrl(QUrl(qurl)) def file_system_locate(self, fpath: str): """This the OS-agnostic "show in Finder" or "show in explorer" equivalent It should open the folder of the item in question Args: fpath (str): [description] """ final_path = os.path.dirname(fpath) while not os.path.isdir(final_path): final_path = os.path.dirname(final_path) qurl = QUrl.fromLocalFile(final_path) QDesktopServices.openUrl(qurl) def toggleSubtree(self, item: QStandardItem = None, expand=True): def _recurse(curritem): idx = self.model.indexFromItem(item) if expand is not self.treeView.isExpanded(idx): self.treeView.setExpanded(idx, expand) for row in range(curritem.rowCount()): _recurse(curritem.child(row)) if item is None: if expand is True: self.treeView.expandAll() else: self.treeView.collapseAll() else: _recurse(item) def add_view_to_map(self, item_data: ProjectTreeData): """Add a view and all its layers to the map Args: item (QStandardItem): [description] """ self.add_children_to_map(item_data.project.qproject, item_data.data) def add_children_to_map(self, item: QStandardItem, bl_ids: List[str] = None): """Iteratively add all children to the map Args: item (QStandardItem): [description] bl_ids (List[str], optional): List of ids to filter by so we don't load everything. this is used for loading views """ for child in self._get_children(item): # Is this something we can add to the map? project_tree_data = child.data(Qt.UserRole) if project_tree_data is not None and isinstance(project_tree_data.data, QRaveMapLayer): data = project_tree_data.data loadme = False # If this layer matches the businesslogic id filter if bl_ids is not None and len(bl_ids) > 0: if 'id' in data.bl_attr and data.bl_attr['id'] in bl_ids: loadme = True else: loadme = True if loadme is True: data.add_layer_to_map(child) def _get_children(self, root_item: QStandardItem): """Recursion is going to kill us here so do an iterative solution instead https://stackoverflow.com/questions/41949370/collect-all-items-in-qtreeview-recursively Yields: [type]: [description] """ stack = [root_item] while stack: parent = stack.pop(0) for row in range(parent.rowCount()): for column in range(parent.columnCount()): child = parent.child(row, column) yield child if child.hasChildren(): stack.append(child) def _get_parents(self, start_item: QStandardItem): stack = [] placeholder = start_item.parent() while placeholder is not None and placeholder != self.model.invisibleRootItem(): stack.append(placeholder) placeholder = start_item.parent() return stack.reverse() def open_menu(self, position): indexes = self.treeView.selectedIndexes() if len(indexes) < 1: return # No multiselect so there is only ever one item idx = indexes[0] if not idx.isValid(): return item = self.model.itemFromIndex(indexes[0]) project_tree_data = item.data(Qt.UserRole) # ProjectTreeData object data = project_tree_data.data # Could be a QRaveBaseMap, a QRaveMapLayer or just some random data # This is the layer context menu if isinstance(data, QRaveMapLayer): if data.layer_type == QRaveMapLayer.LayerTypes.WEBTILE: self.basemap_context_menu(idx, item, project_tree_data) elif data.layer_type in [QRaveMapLayer.LayerTypes.FILE, QRaveMapLayer.LayerTypes.REPORT]: self.file_layer_context_menu(idx, item, project_tree_data) else: self.map_layer_context_menu(idx, item, project_tree_data) elif isinstance(data, QRaveBaseMap): # A WMS QARaveBaseMap is just a container for layers if data.tile_type == 'wms': self.folder_dumb_context_menu(idx, item, project_tree_data) # Every other kind of basemap is an add-able layer else: self.basemap_context_menu(idx, item, project_tree_data) elif project_tree_data.type == QRaveTreeTypes.PROJECT_ROOT: self.project_context_menu(idx, item, project_tree_data) elif project_tree_data.type in [ QRaveTreeTypes.PROJECT_VIEW_FOLDER, QRaveTreeTypes.BASEMAP_ROOT, QRaveTreeTypes.BASEMAP_SUPER_FOLDER ]: self.folder_dumb_context_menu(idx, item, project_tree_data) elif project_tree_data.type in [ QRaveTreeTypes.PROJECT_FOLDER, QRaveTreeTypes.PROJECT_REPEATER_FOLDER, QRaveTreeTypes.BASEMAP_SUB_FOLDER ]: self.folder_context_menu(idx, item, project_tree_data) elif project_tree_data.type == QRaveTreeTypes.PROJECT_VIEW: self.view_context_menu(idx, item, project_tree_data) self.menu.exec_(self.treeView.viewport().mapToGlobal(position)) def map_layer_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_TO_MAP', lambda: QRaveMapLayer.add_layer_to_map(item), enabled=item_data.data.exists) self.menu.addAction('VIEW_LAYER_META', lambda: self.change_meta(item, item_data, True)) if bool(self.get_warehouse_url(item_data.data.meta)): self.menu.addAction('VIEW_WEB_SOURCE', lambda: self.layer_warehouse_view(item_data)) self.menu.addAction('BROWSE_FOLDER', lambda: self.file_system_locate(item_data.data.layer_uri)) def file_layer_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData): self.menu.clear() self.menu.addAction('OPEN_FILE', lambda: self.file_system_open(item_data.data.layer_uri)) self.menu.addAction('BROWSE_FOLDER', lambda: self.file_system_locate(item_data.data.layer_uri)) # Basemap context items def basemap_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_TO_MAP', lambda: QRaveMapLayer.add_layer_to_map(item)) # Folder-level context menu def folder_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_children_to_map(item)) self.menu.addSeparator() self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(item, False)) self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(item, True)) # Some folders don't have the 'ADD_ALL_TO_MAP' functionality enabled def folder_dumb_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(item, False)) self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(item, True)) # View context items def view_context_menu(self, idx: QModelIndex, item: QStandardItem, item_data: ProjectTreeData): self.menu.clear() self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_view_to_map(item_data)) # Project-level context menu def project_context_menu(self, idx: QModelIndex, item: QStandardItem, data: ProjectTreeData): self.menu.clear() self.menu.addAction('COLLAPSE_ALL', lambda: self.toggleSubtree(None, False)) self.menu.addAction('EXPAND_ALL', lambda: self.toggleSubtree(None, True)) self.menu.addSeparator() self.menu.addAction('BROWSE_PROJECT_FOLDER', lambda: self.file_system_locate(data.project.project_xml_path)) self.menu.addAction('VIEW_PROJECT_META', lambda: self.change_meta(item, data, True)) self.menu.addAction('WAREHOUSE_VIEW', lambda: self.project_warehouse_view(data.project), enabled=bool(self.get_warehouse_url(data.project.warehouse_meta))) self.menu.addAction('ADD_ALL_TO_MAP', lambda: self.add_children_to_map(item)) self.menu.addSeparator() self.menu.addAction('REFRESH_PROJECT_HIERARCHY', self.reload_tree) self.menu.addAction('CUSTOMIZE_PROJECT_HIERARCHY', enabled=False) self.menu.addSeparator() self.menu.addAction('CLOSE_PROJECT', lambda: self.close_project(data.project), enabled=bool(data.project))
class MultipleSelectTreeView(QListView): """ Custom QListView implementation that displays checkable items from a multiple select column type. """ def __init__(self, column, parent=None): """ Class constructor. :param column: Multiple select column object. :type column: MultipleSelectColumn :param parent: Parent widget for the control. :type parent: QWidget """ QListView.__init__(self, parent) # Disable editing of lookup values self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.column = column self._item_model = QStandardItemModel(self) self._value_list = self.column.value_list # Stores lookup objects based on primary keys self._lookup_cache = {} self._initialize() self._association = self.column.association self._first_parent_col = self._association.first_reference_column.name self._second_parent_col = self._association.second_reference_column.name # Association model self._assoc_cls = entity_model(self._association) def reset_model(self): """ Resets the item model. """ self._item_model.clear() self._item_model.setColumnCount(2) def clear(self): """ Clears all items in the model. """ self._item_model.clear() @property def association(self): """ :return: Returns the association object corresponding to the column. :rtype: AssociationEntity """ return self._association @property def value_list(self): """ :return: Returns the ValueList object corresponding to the configured column object. :rtype: ValueList """ return self._value_list @property def item_model(self): """ :return: Returns the model corresponding to the checkable items. :rtype: QStandardItemModel """ return self._item_model def _add_item(self, id, value): """ Adds a row corresponding to id and corresponding value from a lookup table. :param id: Primary key of a lookup record. :type id: int :param value: Lookup value :type value: str """ value_item = QStandardItem(value) value_item.setCheckable(True) id_item = QStandardItem(str(id)) self._item_model.appendRow([value_item, id_item]) def _initialize(self): # Populate list with lookup items self.reset_model() # Add all lookup values in the value list table vl_cls = entity_model(self._value_list) if not vl_cls is None: vl_obj = vl_cls() res = vl_obj.queryObject().all() for r in res: self._lookup_cache[r.id] = r self._add_item(r.id, r.value) self.setModel(self._item_model) def clear_selection(self): """ Unchecks all items in the view. """ for i in range(self._item_model.rowCount()): value_item = self._item_model.item(i, 0) if value_item.checkState() == Qt.Checked: value_item.setCheckState(Qt.Unchecked) if value_item.rowCount() > 0: value_item.removeRow(0) def selection(self): """ :return: Returns a list of selected items. :rtype: list """ selection = [] for i in range(self._item_model.rowCount()): value_item = self._item_model.item(i, 0) if value_item.checkState() == Qt.Checked: id_item = self._item_model.item(i, 1) id = int(id_item.text()) # Get item from the lookup cache and append to selection if id in self._lookup_cache: lookup_rec = self._lookup_cache[id] selection.append(lookup_rec) return selection def set_selection(self, models): """ Checks items corresponding to the specified models. :param models: List containing model values in the view for selection. :type models: list """ for m in models: search_value = m.value v_items = self._item_model.findItems(search_value) # Loop through result and check items for vi in v_items: if vi.checkState() == Qt.Unchecked: vi.setCheckState(Qt.Checked)
class DialListCheckBox(object): """ Dialog with list check box example : 1,2,4,6,8 """ def __init__(self, values, checked_values=[]): self.dial = QDialog() self.result = "" # Layout Principal m_vbl = QVBoxLayout(self.dial) # List item checkable view = QListView() m_vbl.addWidget(view) self.m_sim = QStandardItemModel() view.setModel(self.m_sim) self._load_item(values, checked_values) # List buttons bt_all = QPushButton(self.tr("All")) bt_all.clicked.connect(lambda: self._check_all()) bt_nothing = QPushButton(self.tr("Nothing")) bt_nothing.clicked.connect(lambda: self._check_nothing()) bt_print = QPushButton(self.tr("Ok")) bt_print.clicked.connect(lambda: self._check_ok()) # Sub layout for buttons m_vbh = QHBoxLayout() m_vbl.addLayout(m_vbh) m_vbh.addWidget(bt_all) m_vbh.addWidget(bt_nothing) m_vbh.addWidget(bt_print) def _load_item(self, values, checked_values): """Load item list""" for v in values: item = QStandardItem(str(v)) if v in checked_values: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) item.setCheckable(True) self.m_sim.appendRow(item) def _check_all(self): """Check all item""" for index in range(self.m_sim.rowCount( )): ## Iteration sur le model (contient la list des items) item = self.m_sim.item(index) if item.isCheckable() and item.checkState( ) == Qt.Unchecked: # Si Unchecked item.setCheckState(Qt.Checked) def _check_nothing(self): """Uncheck all item""" for index in range(self.m_sim.rowCount( )): ## Iteration sur le model (contient la list des items) item = self.m_sim.item(index) if item.isCheckable() and item.checkState( ) == Qt.Checked: # Si Checked item.setCheckState(Qt.Unchecked) def _check_ok(self): """Get value of checked items and exit""" l_value = [] for index in range(self.m_sim.rowCount( )): ## Iteration sur le model (contient la list des items) item = self.m_sim.item(index) if item.isCheckable() and item.checkState( ) == Qt.Checked: # Si Checked l_value.append(str(item.text())) if l_value: s_value = ";".join(l_value) else: s_value = "" self.result = s_value self.dial.close() def run(self): self.dial.setWindowTitle(self.tr("Select values")) self.dial.exec_() return self.result def tr(self, string, context=''): if context == '' or context == None: context = self.__class__.__name__ return QCoreApplication.translate(context, string)
class DialogImportData(QDialog, DIALOG_UI): def __init__(self, iface, db, qgis_utils): QDialog.__init__(self) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.iface = iface self.db = db self.qgis_utils = qgis_utils self.base_configuration = BaseConfiguration() self.ilicache = IliCache(self.base_configuration) self.ilicache.refresh() self._conf_db = ConfigDbSupported() self._params = None self._current_db = None self.xtf_file_browse_button.clicked.connect( make_file_selector( self.xtf_file_line_edit, title=QCoreApplication.translate( "DialogImportData", 'Open Transfer or Catalog File'), file_filter=QCoreApplication.translate( "DialogImportData", 'Transfer File (*.xtf *.itf);;Catalogue File (*.xml *.xls *.xlsx)' ))) self.validators = Validators() self.xtf_file_line_edit.setPlaceholderText( QCoreApplication.translate("DialogImportData", "[Name of the XTF to be created]")) fileValidator = FileValidator(pattern=['*.xtf', '*.itf', '*.xml']) self.xtf_file_line_edit.setValidator(fileValidator) self.xtf_file_line_edit.textChanged.connect(self.update_import_models) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) # db self.connection_setting_button.clicked.connect(self.show_settings) self.connection_setting_button.setText( QCoreApplication.translate("DialogImportData", 'Connection Settings')) # LOG self.log_config.setTitle( QCoreApplication.translate("DialogImportData", "Show log")) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self._accept_button = self.buttonBox.addButton( QCoreApplication.translate("DialogImportData", "Import data"), QDialogButtonBox.AcceptRole) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.show_help) self.update_connection_info() self.restore_configuration() def update_connection_info(self): db_description = self.db.get_description_conn_string() if db_description: self.db_connect_label.setText(db_description) self.db_connect_label.setToolTip(self.db.get_display_conn_string()) self._accept_button.setEnabled(True) else: self.db_connect_label.setText( QCoreApplication.translate("DialogImportData", "The database is not defined!")) self.db_connect_label.setToolTip('') self._accept_button.setEnabled(False) def update_import_models(self): message_error = None if not self.xtf_file_line_edit.text().strip(): color = '#ffd356' # Light orange self.import_models_qmodel = QStandardItemModel() self.import_models_list_view.setModel(self.import_models_qmodel) else: if os.path.isfile(self.xtf_file_line_edit.text().strip()): color = '#fff' # White self.import_models_qmodel = QStandardItemModel() models_name = self.find_models_xtf( self.xtf_file_line_edit.text().strip()) for model_name in models_name: if not model_name in DEFAULT_HIDDEN_MODELS: item = QStandardItem(model_name) item.setCheckable(False) item.setEditable(False) self.import_models_qmodel.appendRow(item) if self.import_models_qmodel.rowCount() > 0: self.import_models_list_view.setModel( self.import_models_qmodel) else: message_error = QCoreApplication.translate( "DialogImportData", "No models were found in the XTF. Is it a valid file?") color = '#ffd356' # Light orange self.import_models_qmodel = QStandardItemModel() self.import_models_list_view.setModel( self.import_models_qmodel) else: message_error = QCoreApplication.translate( "DialogImportData", "Please set a valid XTF file") color = '#ffd356' # Light orange self.import_models_qmodel = QStandardItemModel() self.import_models_list_view.setModel( self.import_models_qmodel) self.xtf_file_line_edit.setStyleSheet( 'QLineEdit {{ background-color: {} }}'.format(color)) if message_error: self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.import_models_list_view.setFocus() return def find_models_xtf(self, xtf_path): models_name = list() pattern = re.compile(r'<MODEL[^>]*>(?P<text>[^<]*)</MODEL>') with open(xtf_path, 'r') as f: for txt in pattern.finditer(f.read()): model_tag = str(txt.group(0)) name = re.findall('NAME="(.*?)"', model_tag, re.IGNORECASE) models_name.extend(name) return models_name def get_ili_models(self): ili_models = list() for index in range(self.import_models_qmodel.rowCount()): item = self.import_models_qmodel.item(index) ili_models.append(item.text()) return ili_models def show_settings(self): dlg = self.qgis_utils.get_settings_dialog() dlg.set_action_type(EnumDbActionType.IMPORT) dlg.tabWidget.setCurrentIndex(SETTINGS_CONNECTION_TAB_INDEX) if dlg.exec_(): self.db = dlg.get_db_connection() self.update_connection_info() def accepted(self): configuration = self.update_configuration() if not os.path.isfile(self.xtf_file_line_edit.text().strip()): message_error = 'Please set a valid XTF file before importing data. XTF file does not exist' self.txtStdout.setText( QCoreApplication.translate("DialogImportData", message_error)) self.show_message(message_error, Qgis.Warning) self.xtf_file_line_edit.setFocus() return if not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable: message_error = 'Please set a valid XTF before importing data.' self.txtStdout.setText( QCoreApplication.translate("DialogImportData", message_error)) self.show_message(message_error, Qgis.Warning) self.xtf_file_line_edit.setFocus() return if not self.get_ili_models(): message_error = QCoreApplication.translate( "DialogImportData", "The selected XTF file does not have information according to the LADM-COL model to import." ) self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.import_models_list_view.setFocus() return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.progress_bar.setValue(0) self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() dataImporter = iliimporter.Importer(dataImport=True) item_db = self._conf_db.get_db_items()[self.db.mode] dataImporter.tool_name = item_db.get_model_baker_tool_name() dataImporter.configuration = configuration self.save_configuration(configuration) dataImporter.stdout.connect(self.print_info) dataImporter.stderr.connect(self.on_stderr) dataImporter.process_started.connect(self.on_process_started) dataImporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if dataImporter.run() != iliimporter.Importer.SUCCESS: self.enable() self.progress_bar.hide() self.show_message( QCoreApplication.translate( "DialogImportData", "An error occurred when importing the data. For more information see the log..." ), Qgis.Warning) return except JavaNotFoundError: # Set JAVA PATH get_java_path_dlg = DialogGetJavaPath() get_java_path_dlg.setModal(True) if get_java_path_dlg.exec_(): configuration = self.update_configuration() if not get_java_path_from_qgis_model_baker(): message_error_java = QCoreApplication.translate( "DialogImportData", """Java could not be found. You can configure the JAVA_HOME environment variable, restart QGIS and try again.""" ) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(message_error_java) self.show_message(message_error_java, Qgis.Warning) self.enable() self.progress_bar.hide() return self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) self.show_message( QCoreApplication.translate( "DialogImportData", "Import of the data was successfully completed"), Qgis.Success) def save_configuration(self, configuration): settings = QSettings() settings.setValue( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_import', configuration.xtffile) settings.setValue('Asistente-LADM_COL/QgisModelBaker/show_log', not self.log_config.isCollapsed()) def restore_configuration(self): settings = QSettings() self.xtf_file_line_edit.setText( settings.value( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_import')) # Show log value_show_log = settings.value( 'Asistente-LADM_COL/QgisModelBaker/show_log', False, type=bool) self.log_config.setCollapsed(not value_show_log) # set model repository # if there is no option by default use online model repository self.use_local_models = settings.value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', type=bool) if self.use_local_models: self.custom_model_directories = settings.value( 'Asistente-LADM_COL/models/custom_models') if settings.value( 'Asistente-LADM_COL/models/custom_models') else None def update_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ item_db = self._conf_db.get_db_items()[self.db.mode] configuration = ImportDataConfiguration() item_db.set_db_configuration_params(self.db.dict_conn_params, configuration) configuration.xtffile = self.xtf_file_line_edit.text().strip() configuration.delete_data = False configuration.epsg = DEFAULT_EPSG configuration.inheritance = DEFAULT_INHERITANCE configuration.create_basket_col = CREATE_BASKET_COL configuration.create_import_tid = CREATE_IMPORT_TID configuration.stroke_arcs = STROKE_ARCS java_path = get_java_path_from_qgis_model_baker() if java_path: self.base_configuration.java_path = java_path # Check custom model directories if self.use_local_models: if self.custom_model_directories is None: self.base_configuration.custom_model_directories_enabled = False else: self.base_configuration.custom_model_directories = self.custom_model_directories self.base_configuration.custom_model_directories_enabled = True configuration.base_configuration = self.base_configuration if self.get_ili_models(): configuration.ilimodels = ';'.join(self.get_ili_models()) configuration.disable_validation = not QSettings().value( 'Asistente-LADM_COL/advanced_settings/validate_data_importing_exporting', True, bool) return configuration def print_info(self, text, text_color='#000000', clear=False): self.txtStdout.setTextColor(QColor(text_color)) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = '#004905' if exit_code == 0 else '#aa2222' self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append('Finished ({})'.format(exit_code)) if result == iliimporter.Importer.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: self.show_message( QCoreApplication.translate("DialogImportData", "Error when importing data"), Qgis.Warning) self.enable() # Open log if self.log_config.isCollapsed(): self.log_config.setCollapsed(False) def advance_progress_bar_by_text(self, text): if text.strip() == 'Info: compile models...': self.progress_bar.setValue(50) QCoreApplication.processEvents() elif text.strip() == 'Info: create table structure...': self.progress_bar.setValue(55) QCoreApplication.processEvents() elif text.strip() == 'Info: first validation pass...': self.progress_bar.setValue(60) QCoreApplication.processEvents() elif text.strip() == 'Info: second validation pass...': self.progress_bar.setValue(80) QCoreApplication.processEvents() def show_help(self): self.qgis_utils.show_help("import_data") def disable(self): self.db_config.setEnabled(False) self.ili_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.db_config.setEnabled(True) self.ili_config.setEnabled(True) self.buttonBox.setEnabled(True) def show_message(self, message, level): self.bar.pushMessage("Asistente LADM_COL", message, level, duration=0)
class TemporalSpectralProfilePlugin(object): POINT_SELECTION = 0 SELECTED_POLYGON = 1 def __init__(self, iface): self.iface = iface self.canvas = iface.mapCanvas() self.wdg = None self.pointTool = None def initGui(self): # create action self.action = QAction( QIcon( ":/plugins/temporalprofiletool/icons/temporalProfileIcon.png"), "Temporal/Spectral Profile", self.iface.mainWindow()) self.action.setWhatsThis("Plots temporal/spectral profiles") self.action.triggered.connect(self.run) self.aboutAction = QAction("About", self.iface.mainWindow()) self.aboutAction.triggered.connect(self.about) # add toolbar button and menu item self.iface.addToolBarIcon(self.action) self.iface.addPluginToMenu("&Profile Tool", self.action) self.iface.addPluginToMenu("&Profile Tool", self.aboutAction) #Init class variables self.pointTool = ProfiletoolMapTool(self.iface.mapCanvas(), self.action) #the mouselistener self.dockOpened = False #remember for not reopening dock if there's already one opened self.mdl = None #the model whitch in are saved layers analysed caracteristics self.selectionmethod = 0 #The selection method defined in option self.saveTool = self.canvas.mapTool( ) #Save the standard mapttool for restoring it at the end self.plotlibrary = None #The plotting library to use self.pointSelectionInstructions = "Click on a raster for temporal/spectral profile (right click to cancel then quit)" self.selectedPolygonInstructions = 'Use "Select Features" tool to select polygon(s) designating AOI for which temporal/spectral profile should be calculated' def unload(self): if not self.wdg is None: self.wdg.close() self.iface.removeToolBarIcon(self.action) self.iface.removePluginMenu("&Profile Tool", self.action) self.iface.removePluginMenu("&Profile Tool", self.aboutAction) def run(self): # first, check posibility if self.checkIfOpening() == False: return #if dock not already opened, open the dock and all the necessary thing (model,doProfile...) if self.dockOpened == False: self.mdl = QStandardItemModel(0, 6) self.wdg = PTDockWidget(self.iface.mainWindow(), self.iface, self.mdl) self.wdg.showIt() self.doprofile = DoProfile(self.iface, self.wdg, self.pointTool, self) self.tableViewTool = TableViewTool() self.wdg.closed.connect(self.cleaning2) self.wdg.tableView.clicked.connect(self._onClick) self.wdg.pushButton_2.clicked.connect(self.addLayer) self.wdg.pushButton.clicked.connect(self.removeLayer) self.wdg.comboBox.currentIndexChanged.connect(self.selectionMethod) self.wdg.cboLibrary.currentIndexChanged.connect( self.changePlotLibrary) self.wdg.cboXAxis.currentIndexChanged.connect( self.changeXAxisLabeling) self.wdg.leXAxisSteps.editingFinished.connect( self.changeXAxisLabeling) self.wdg.dateTimeEditCurrentTime.editingFinished.connect( self.changeXAxisLabeling) self.wdg.spinBoxTimeExtent.editingFinished.connect( self.changeXAxisLabeling) self.wdg.cboTimeExtent.currentIndexChanged.connect( self.changeXAxisLabeling) self.wdg.cbTimeDimension.stateChanged.connect( self.changeXAxisLabeling) self.wdg.addOptionComboboxItems() self.addLayer() self.dockOpened = True #Listeners of mouse self.connectPointMapTool() #init the mouse listener comportement and save the classic to restore it on quit self.canvas.setMapTool(self.pointTool) #Help about what doing if self.selectionmethod == TemporalSpectralProfilePlugin.POINT_SELECTION: self.iface.mainWindow().statusBar().showMessage( self.pointSelectionInstructions) elif self.selectionmethod == TemporalSpectralProfilePlugin.SELECTED_POLYGON: self.iface.mainWindow().statusBar().showMessage( self.selectedPolygonInstructions) QgsProject.instance().layersWillBeRemoved.connect( self.onLayersWillBeRemoved) #************************************* Canvas listener actions ********************************************** # Used when layer is about to be removed from QGIS Map Layer Registry def onLayersWillBeRemoved(self, layersIds): if self.mdl is not None: for layerId in layersIds: for row in range(self.mdl.rowCount()): if layerId == self.mdl.index(row, 3).data().id(): self.removeLayer(row) self.onLayersWillBeRemoved(layersIds) break # Use for selected polygon option def selectionChanged(self, layer): if not layer.geometryType() == QgsWkbTypes.PolygonGeometry: return fullGeometry = QgsGeometry() for feature in layer.selectedFeatures(): if fullGeometry.isEmpty(): fullGeometry = QgsGeometry(feature.geometry()) else: fullGeometry = fullGeometry.combine(feature.geometry()) if not fullGeometry.isEmpty(): crs = osr.SpatialReference() crs.ImportFromProj4(str(layer.crs().toProj4())) self.doprofile.calculatePolygonProfile(fullGeometry, crs, self.mdl, self.plotlibrary) #************************************* Mouse listener actions *********************************************** # Used for point selection option def moved(self, point): if self.wdg and not self.wdg.cbPlotWhenClick.isChecked(): if self.selectionmethod == TemporalSpectralProfilePlugin.POINT_SELECTION: self.doubleClicked(point) if self.selectionmethod == TemporalSpectralProfilePlugin.SELECTED_POLYGON: pass def rightClicked(self, point): #used to quit the current action self.cleaning() def leftClicked(self, point): self.doubleClicked(point) def doubleClicked(self, point): if self.selectionmethod == TemporalSpectralProfilePlugin.POINT_SELECTION: self.iface.mainWindow().statusBar().showMessage( str(point.x()) + ", " + str(point.y())) self.doprofile.calculatePointProfile(point, self.mdl, self.plotlibrary) if self.selectionmethod == TemporalSpectralProfilePlugin.SELECTED_POLYGON: return #***************************** open and quit options ******************************************* def checkIfOpening(self): if self.iface.mapCanvas().layerCount() == 0: #Check a layer is opened QMessageBox.warning(self.iface.mainWindow(), "Profile", "First open a raster layer, please") return False layer = self.iface.activeLayer() if layer == None or not isProfilable( layer): #Check if a raster layer is opened and selected if self.mdl == None or self.mdl.rowCount() == 0: QMessageBox.warning(self.iface.mainWindow(), "Profile Tool", "Please select a raster layer") return False return True def connectPointMapTool(self): self.pointTool.moved.connect(self.moved) self.pointTool.rightClicked.connect(self.rightClicked) self.pointTool.leftClicked.connect(self.leftClicked) self.pointTool.doubleClicked.connect(self.doubleClicked) def deactivatePointMapTool(self): #enable clean exit of the plugin self.pointTool.moved.disconnect(self.moved) self.pointTool.leftClicked.disconnect(self.leftClicked) self.pointTool.rightClicked.disconnect(self.rightClicked) self.pointTool.doubleClicked.disconnect(self.doubleClicked) self.iface.mainWindow().statusBar().showMessage("") def connectSelectedPolygonsTool(self): self.iface.mapCanvas().selectionChanged.connect(self.selectionChanged) def deactivateSelectedPolygonsTools(self): self.iface.mapCanvas().selectionChanged.disconnect( self.selectionChanged) def cleaning(self): #used on right click self.canvas.unsetMapTool(self.pointTool) self.canvas.setMapTool(self.saveTool) self.iface.mainWindow().statusBar().showMessage("") def cleaning2(self): #used when Dock dialog is closed self.wdg.tableView.clicked.disconnect(self._onClick) if self.selectionmethod == TemporalSpectralProfilePlugin.POINT_SELECTION: self.deactivatePointMapTool() else: self.deactivateSelectedPolygonsTools() self.selectionmethod = TemporalSpectralProfilePlugin.POINT_SELECTION self.wdg.comboBox.currentIndexChanged.disconnect(self.selectionMethod) QgsProject.instance().layersWillBeRemoved.disconnect( self.onLayersWillBeRemoved) self.mdl = None self.dockOpened = False self.cleaning() self.wdg = None #***************************** Options ******************************************* def selectionMethod(self, item): if item == TemporalSpectralProfilePlugin.POINT_SELECTION: self.selectionmethod = TemporalSpectralProfilePlugin.POINT_SELECTION self.pointTool.setCursor(Qt.CrossCursor) self.deactivateSelectedPolygonsTools() self.connectPointMapTool() if not self.canvas.mapTool() == self.pointTool: self.canvas.setMapTool(self.pointTool) self.iface.mainWindow().statusBar().showMessage( self.pointSelectionInstructions) self.wdg.changeStatComboBoxItems( self.doprofile.getPointProfileStatNames()) elif item == TemporalSpectralProfilePlugin.SELECTED_POLYGON: self.selectionmethod = TemporalSpectralProfilePlugin.SELECTED_POLYGON self.deactivatePointMapTool() self.connectSelectedPolygonsTool() self.iface.actionSelectRectangle().trigger() self.iface.mainWindow().statusBar().showMessage( self.selectedPolygonInstructions) self.wdg.changeStatComboBoxItems( self.doprofile.getPolygonProfileStatNames(), "mean") def changePlotLibrary(self, item): self.plotlibrary = self.wdg.cboLibrary.itemText(item) self.wdg.addPlotWidget(self.plotlibrary) self.changeXAxisLabeling() def changeXAxisLabeling(self): self.xAxisSteps = {} # default band number labeling if self.wdg.cboXAxis.currentIndex() == 0: self.doprofile.xAxisSteps = None # Labels from string elif self.wdg.cboXAxis.currentIndex() == 1: self.doprofile.xAxisSteps = self.wdg.leXAxisSteps.text().split(';') try: self.doprofile.xAxisSteps = [ float(x) for x in self.doprofile.xAxisSteps ] except ValueError: self.doprofile.xAxisSteps = None text = "Temporal/Spectral Profile Tool: The X-axis steps' string " + \ "is invalid. Using band numbers instead." self.iface.messageBar().pushWidget( self.iface.messageBar().createMessage(text), QgsMessageBar.WARNING, 5) # Labels based on time elif self.wdg.cboXAxis.currentIndex() == 2: self.doprofile.xAxisSteps = [ "Timesteps", self.wdg.dateTimeEditCurrentTime.dateTime().toPyDateTime(), int(self.wdg.spinBoxTimeExtent.cleanText()), self.wdg.cboTimeExtent.currentText(), self.wdg.cbTimeDimension.isChecked() ] if self.plotlibrary == "Qwt5": text = "Temporal/Spectral Profile Tool: There is currently no support using " + \ "Time steps while using the Qwt plotlibrary" self.iface.messageBar().pushWidget( self.iface.messageBar().createMessage(text), QgsMessageBar.WARNING, 5) self.doprofile.xAxisSteps = None #************************* tableview function ****************************************** def addLayer(self): layer = self.iface.activeLayer() if isProfilable(layer): self.tableViewTool.addLayer(self.iface, self.mdl, layer) def _onClick(self, index1): #action when clicking the tableview self.tableViewTool.onClick(self.iface, self.wdg, self.mdl, self.plotlibrary, index1) def removeLayer(self, index=None): if index is None or index is False: index = self.tableViewTool.chooseLayerForRemoval( self.iface, self.mdl) if index is not None: self.tableViewTool.removeLayer(self.mdl, index) PlottingTool().clearData(self.wdg, self.mdl, self.plotlibrary) def about(self): from .ui.dlgabout import DlgAbout DlgAbout(self.iface.mainWindow()).exec_()
class Dialog(QDialog, Ui_nbEditor_dialog): def __init__(self, iface, ml, mc): """Constructor for the dialog. Args: iface: QgsInterface instance. """ QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.ml = ml self.mCanvas = mc self.mRubberBand = QgsRubberBand(self.mCanvas, True) self.mRubberBand.reset(QgsWkbTypes.PolygonGeometry) self.mRubberBand.setColor(Qt.red) self.mRubberBand.setWidth(2) self.ids = [] self.ini(0) self.pushCancel.clicked.connect(self.close) self.pushOK.clicked.connect(self.convert) self.comboBox.addItems( ['', 'Intersections', 'Touches', 'Within distance']) self.comboBox.currentIndexChanged.connect(self.nbMethod) self.ml.selectionChanged.connect(self.map2tab) def ini(self, n): self.model = QStandardItemModel(n, 1) self.tableView.setModel(self.model) self.model.setHeaderData(0, Qt.Horizontal, 'Neighbouring IDs') self.tableView.setSelectionMode(QAbstractItemView.SingleSelection) self.selectionModel = QItemSelectionModel(self.model) self.tableView.setSelectionModel(self.selectionModel) self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.selectionModel().selectionChanged.connect(self.tab2map) self.progressBar.setValue(0) def settings(self): self.mod = min(self.ids) self.p = 1 if self.mod == 1: self.p = 0 def map2tab(self): s = '' idx = self.tableView.selectionModel().selectedIndexes()[0] ts = str(self.model.itemData(idx)[0]) for fid in sorted(self.ml.selectedFeatureIds()): s += '%s,' % str(int(fid) + self.p) s = s[:-1] if s != ts: self.model.setData(idx, s) # in order to handle the symmetry if len(s) > len(ts): iLst = s.strip().replace(' ', '').split(',') jLst = ts.strip().replace(' ', '').split(',') else: iLst = ts.strip().replace(' ', '').split(',') jLst = s.strip().replace(' ', '').split(',') cent = str(idx.row() + self.p) dLst = list(set(iLst) - set(jLst)) for d in dLst: row = int(d) - self.p sor = str(self.model.itemData(self.model.index(row, 0))[0]) eLst = sor.strip().replace(' ', '').split(',') res = '' if cent in set(eLst): ii = eLst.index(cent) del eLst[ii] eLst = sorted(map(int, eLst)) for e in eLst: res += '%s,' % e res = res[:-1] else: u = sor + ',%s' % cent eLst = sorted(map(int, u.strip().replace(' ', '').split(','))) for e in eLst: res += '%s,' % e res = res[:-1] self.model.setData(self.model.index(row, 0, QModelIndex()), res) def nbWithinDist(self): dlg = xdist.Dialog() dlg.setModal(True) dlg.setWindowTitle("Between two objects") if dlg.exec_() == QDialog.Accepted: lDist = float(dlg.lineEdit.text()) if lDist == 0: return feat = QgsFeature() provider = self.ml.dataProvider() e = provider.featureCount() self.settings() for ne in range(self.mod, e + self.mod): feat = QgsFeature() geom = QgsGeometry() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): geom = QgsGeometry(feat.geometry()) neighbours = self.hdist(feat, lDist) row = feat.id() - self.mod self.model.setData(self.model.index(row, 0, QModelIndex()), neighbours) self.progressBar.setValue(100 * ne / e) def hdist(self, feata, lDist): geoma = QgsGeometry(feata.geometry()) feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) #self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, provider.featureCount())) ne = 0 neighbours = "" while feats.nextFeature(feat): ne += 1 #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) geomb = QgsGeometry(feat.geometry()) if feata.id() != feat.id(): if geoma.distance(geomb) <= lDist: neighbours = neighbours + '%s,' % (feat.id() + self.p) return neighbours[:-1] def tab2map(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.ml.selectionChanged.disconnect(self.map2tab) idx = self.tableView.selectionModel().selectedIndexes()[0] featureId = idx.row() + self.p s = self.model.itemData(idx) lst = s[0].strip().replace(' ', '').split(',') self.ml.removeSelection() for sid in lst: self.ml.select(int(sid) - self.p) provider = self.ml.dataProvider() feat = QgsFeature() layer = QgsVectorLayerCache(self.ml, provider.featureCount()) layer.featureAtId(idx.row() + self.mod, feat) geom = QgsGeometry(feat.geometry()) self.mRubberBand.setToGeometry(geom, self.ml) self.mRubberBand.show() self.ml.selectionChanged.connect(self.map2tab) QApplication.restoreOverrideCursor() def closeEvent(self, event): QApplication.setOverrideCursor(Qt.WaitCursor) self.ml.selectionChanged.disconnect(self.map2tab) self.ml.removeSelection() self.mRubberBand.hide() self.close() QApplication.restoreOverrideCursor() def convert(self): dlg = editordlg() dlg.setModal(True) dlg.setWindowTitle("Neighbour list in BUGS format") num = "" adj = "" sumNumNeigh = 0 for row in range(0, self.model.rowCount()): ts = self.model.itemData(self.model.index(row, 0)) lst = ts[0].strip().replace(' ', '').split(',') num += '%s, ' % len(lst) sumNumNeigh += len(lst) lst.reverse() sor = ', '.join(lst) + ',' adj = adj + str(sor) + '\n' num = num[:-2] adj = adj[:-2] nblist = 'list(\nnum = c(%s),\nadj = c(%s),\nsumNumNeigh=%s)' % ( num, adj, sumNumNeigh) dlg.plainTextEdit.appendPlainText(nblist) dlg.exec_() def nbMethod(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.ml.selectionChanged.disconnect(self.map2tab) self.model.removeRows(0, self.model.rowCount(QModelIndex()), QModelIndex()) n = self.ml.dataProvider().featureCount() self.ini(n) self.ids = [] provider = self.ml.dataProvider() feats = provider.getFeatures() #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) #self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, n)) ne = 0 feat = QgsFeature() while feats.nextFeature(feat): ne += 1 #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) self.ids.append(feat.id()) if self.comboBox.currentText() == "Touches": if self.ml.geometryType() == 0: return else: self.nbTouches() if self.comboBox.currentText() == "Intersections": if self.ml.geometryType() == 0: return else: self.nbIntersects() if self.comboBox.currentText() == "Within distance": self.nbWithinDist() self.ml.selectionChanged.connect(self.map2tab) QApplication.restoreOverrideCursor() def nbTouches(self): feat = QgsFeature() provider = self.ml.dataProvider() e = provider.featureCount() self.settings() for ne in range(self.mod, e + self.mod): feat = QgsFeature() geom = QgsGeometry() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): geom = QgsGeometry(feat.geometry()) neighbours = self.htouch(feat) row = feat.id() - self.mod self.model.setData(self.model.index(row, 0, QModelIndex()), neighbours) self.progressBar.setValue(100 * ne / e) def htouch(self, feata): geoma = QgsGeometry(feata.geometry()) feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) #self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, provider.featureCount())) ne = 0 neighbours = "" while feats.nextFeature(feat): ne += 1 #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) geomb = QgsGeometry(feat.geometry()) if feata.id() != feat.id(): if geoma.touches(geomb) == True: neighbours = neighbours + '%s,' % (feat.id() + self.p) return neighbours[:-1] def nbIntersects(self): feat = QgsFeature() provider = self.ml.dataProvider() e = provider.featureCount() self.settings() for ne in range(self.mod, e + self.mod): feat = QgsFeature() geom = QgsGeometry() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): geom = QgsGeometry(feat.geometry()) neighbours = self.hintersect(feat) row = feat.id() - self.mod self.model.setData(self.model.index(row, 0, QModelIndex()), neighbours) self.progressBar.setValue(100 * ne / e) def hintersect(self, feata): geoma = QgsGeometry(feata.geometry()) feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) #self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, provider.featureCount())) ne = 0 neighbours = "" while feats.nextFeature(feat): ne += 1 #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) geomb = QgsGeometry(feat.geometry()) if feata.id() != feat.id(): if geoma.intersects(geomb) == True: neighbours = neighbours + '%s,' % (feat.id() + self.p) return neighbours[:-1]
class ThinGreyscaleDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): """Constructor.""" self.iface = iface self.plugin_dir = dirname(__file__) self.THINGREYSCALE = self.tr('ThinGreyscale') self.BROWSE = self.tr('Browse') self.CANCEL = self.tr('Cancel') self.CLOSE = self.tr('Close') self.HELP = self.tr('Help') self.OK = self.tr('OK') self.DEFAULTPROVIDER = 'GTiff' self.DEFAULTEXTENSION = '.tif' self.EXTRAEXTENSION = ' *.tiff' super(ThinGreyscaleDialog, 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) self.showInfo("Connecting UI components") okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) cancelButton = self.button_box.button(QDialogButtonBox.Cancel) cancelButton.setText(self.CANCEL) cancelButton.setEnabled(False) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) browseButton = self.browseButton browseButton.setText(self.BROWSE) self.calcHistPushButton.setEnabled(False) self.listModel = QStandardItemModel(self.levelsListView) self.levelsListView.setModel(self.listModel) self.levelsListView.sizeHintForColumn(20) #self.levelValuesCheckBox.setEnabled(False) # Help button helpButton = self.helpButton helpButton.setText(self.HELP) # Connect signals self.showInfo("Connecting signals") okButton.clicked.connect(self.startWorker) cancelButton.clicked.connect(self.killWorker) closeButton.clicked.connect(self.reject) helpButton.clicked.connect(self.help) browseButton.clicked.connect(self.browse) inpIndexCh = self.inputRaster.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) bandCh = self.bandComboBox.currentIndexChanged['QString'] bandCh.connect(self.bandChanged) #self.iface.legendInterface().itemAdded.connect( # self.layerlistchanged) #self.iface.legendInterface().itemRemoved.connect( # self.layerlistchanged) #QObject.disconnect(self.button_box, SIGNAL("rejected()"), self.reject) self.button_box.rejected.disconnect(self.reject) calchistPr = self.calcHistPushButton.clicked calchistPr.connect(self.calculateHistogram) sugglevPr = self.suggestlevelsPushButton.clicked sugglevPr.connect(self.suggestLevels) addlevPr = self.addlevelPushButton.clicked addlevPr.connect(self.addLevel) dellevPr = self.deletelevelsPushButton.clicked dellevPr.connect(self.removeLevel) maxvalCh = self.maxValueSpinBox.valueChanged maxvalCh.connect(self.minmaxvalueChanged) maxvalFi = self.maxValueSpinBox.editingFinished maxvalFi.connect(self.minmaxvalueEdFinished) minvalCh = self.minValueSpinBox.valueChanged minvalCh.connect(self.minmaxvalueChanged) minvalFi = self.minValueSpinBox.editingFinished minvalFi.connect(self.minmaxvalueEdFinished) # Set instance variables #self.mem_layer = None self.worker = None self.inputlayerid = None self.inputlayer = None self.layerlistchanging = False self.minvalue = 1 self.inputrasterprovider = None self.histobins = 50 self.setupScene = QGraphicsScene(self) self.histoGraphicsView.setScene(self.setupScene) # Is the layer band of an integer type self.intband = False self.histogramAvailable = False self.histo = None self.histopadding = 1 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 workerFinished(self, ok, ret): """Handles the output from the worker and cleans up after the worker has finished.""" # clean up the worker and thread self.showInfo("Handling the result") self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() # remove widget from message bar (pop) self.iface.messageBar().popWidget(self.messageBar) if ok and ret is not None: #self.showInfo("Ret: "+str(ret[10,])) # Transformation: self.minx = self.thinninglayer.extent().xMinimum() self.maxx = self.thinninglayer.extent().xMaximum() self.miny = self.thinninglayer.extent().yMinimum() self.maxy = self.thinninglayer.extent().yMaximum() self.rows = self.thinninglayer.height() self.cols = self.thinninglayer.width() self.xres = (self.maxx - self.minx) / float(self.cols) self.yres = (self.maxy - self.miny) / float(self.rows) geotransform = (self.minx, self.xres, 0, self.maxy, 0, -self.yres) try: format = self.DEFAULTPROVIDER driver = gdal.GetDriverByName(format) NOVALUE = 0 metadata = driver.GetMetadata() fileName = self.outputRaster.text() if self.outputRaster.text() == "": self.showInfo("No output file specified, " + "creating a temporary file") # Get a temporary file fileName = mktemp(prefix='greyskel', suffix=self.DEFAULTEXTENSION) fileInfo = QFileInfo(fileName) filepath = fileInfo.absolutePath() baseName = fileInfo.baseName() suffix = fileInfo.suffix() thisfilename = filepath + baseName + '.' + suffix thisfilename = fileName self.showInfo("File name: " + thisfilename) gdaldatatype = gdal.GDT_Byte skelmatrix = None if self.levelValuesCheckBox.isChecked(): # Transform the pixel values back to the original # level values my_dict = {} # Add zero to handle the "empty" pixels my_dict[0] = 0 for i in range(len(self.levels)): my_dict[i + 1] = self.levels[i] skelmatrix = np.vectorize(my_dict.__getitem__, otypes=[np.float])(ret) gdaldatatype = gdal.GDT_Int32 if not self.intband: gdaldatatype = gdal.GDT_Float32 else: skelmatrix = ret outDataset = driver.Create(thisfilename, self.cols, self.rows, 1, gdaldatatype) if self.thinninglayer.dataProvider().crs() is not None: srs = self.thinninglayer.dataProvider().crs() outDataset.SetProjection(srs.toWkt().encode('ascii', 'ignore')) skeletonband = outDataset.GetRasterBand(1) skeletonband.WriteArray(skelmatrix) skeletonband.SetNoDataValue(NOVALUE) #stats = skeletonband.GetStatistics(False, True) #skeletonband.SetStatistics(stats[0], stats[1], # stats[2], stats[3]) outDataset.SetGeoTransform(geotransform) outDataset = None # To close the file # report the result rlayer = QgsRasterLayer(thisfilename, baseName) self.layerlistchanging = True #QgsMapLayerRegistry.instance().addMapLayer(rlayer) QgsProject.instance().addMapLayer(rlayer) self.layerlistchanging = False except: import traceback self.showError("Can't write the skeleton file: %s" % self.outputRaster.text() + ' - ' + traceback.format_exc()) okb = self.button_box.button(QDialogButtonBox.Ok) okb.setEnabled(True) closb = self.button_box.button(QDialogButtonBox.Close) closb.setEnabled(True) cancb = self.button_box.button(QDialogButtonBox.Cancel) cancb.setEnabled(False) return QgsMessageLog.logMessage(self.tr('ThinGreyscale finished'), self.THINGREYSCALE, Qgis.Info) else: # notify the user that something went wrong if not ok: self.showError(self.tr('Aborted') + '!') else: self.showError(self.tr('No skeleton created') + '!') self.progressBar.setValue(0.0) #self.aprogressBar.setValue(0.0) self.iterProgressBar.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) def workerError(self, exception_string): """Report an error from the worker.""" #QgsMessageLog.logMessage(self.tr('Worker failed - exception') + # ': ' + str(exception_string), # self.THINGREYSCALE, # QgsMessageLog.CRITICAL) self.showError(exception_string) def workerInfo(self, message_string): """Report an info message from the worker.""" QgsMessageLog.logMessage(self.tr('Worker') + ': ' + message_string, self.THINGREYSCALE, Qgis.Info) def layerchanged(self, number=0): """Do the necessary updates after a layer selection has been changed.""" self.showInfo("Layer changed") # If the layer list is being updated, don't do anything if self.layerlistchanging: return layerindex = self.inputRaster.currentIndex() layerId = self.inputRaster.itemData(layerindex) self.inputlayerid = layerId #self.inputlayer = QgsMapLayerRegistry.instance().mapLayer(layerId) self.inputlayer = QgsProject.instance().mapLayer(layerId) if self.inputlayer is not None: self.inputrasterprovider = self.inputlayer.dataProvider() self.bandComboBox.clear() bandcount = self.inputlayer.bandCount() #self.showInfo("Layer bandcount: "+str(bandcount)) for i in range(bandcount): self.bandComboBox.addItem(self.inputlayer.bandName(i + 1), i) #self.showInfo("Band " + str(i) + ": " + # self.inputlayer.bandName(i+1)) # Check if the driver supports Create() or CreateCopy() #gdalmetadata = self.inputlayer.metadata() #self.showInfo("Layer metadata: " + # str(gdalmetadata.encode('utf-8'))) #provstring = '<p>GDAL provider</p>\n' #providerpos = gdalmetadata.find(provstring) #brpos = gdalmetadata.find('<br>', providerpos + len(provstring)) #self.gdalprovider = gdalmetadata[int(providerpos + # len(provstring)):int(brpos)] #self.showInfo('GDAL provider: '+self.gdalprovider) #drivername = self.gdalprovider.encode('ascii', 'ignore') #theDriver = gdal.GetDriverByName(drivername) #if theDriver is None: # self.showInfo("Unable to get the raster driver") #else: #data theMetadata = theDriver.GetMetadata() #self.showInfo("Driver metadata: "+str(theMetadata)) #if ((gdal.DCAP_CREATE in theMetadata) and # theMetadata[gdal.DCAP_CREATE] == 'YES'): # self.canCreate = True #if (theMetadata.has_key(gdal.DCAP_CREATECOPY) and #if ((gdal.DCAP_CREATECOPY in theMetadata) and # theMetadata[gdal.DCAP_CREATECOPY] == 'YES'): # self.canCreateCopy = True #self.showInfo('raster provider type: ' + # str(self.inputlayer.providerType())) # Determine the file suffix #self.gdalext = "" #if gdal.DMD_EXTENSION in theMetadata: # self.gdalext = "." + theMetadata[gdal.DMD_EXTENSION] #else: # self.showInfo("No extension available in GDAL metadata") # by parsing the layer metadata looking for # "Dataset Description" #descstring = 'Dataset Description</p>\n<p>' #descpos = gdalmetadata.find(descstring) #ppos = gdalmetadata.find('</p>',descpos+len(descstring)) #filename = gdalmetadata[descpos+len(descstring):ppos] #self.gdalext = splitext(filename)[1] #self.showInfo('GDAL extension: '+self.gdalext) # Determine the datatype #datatypestring = 'Data Type</p>\n<p>' #datatypepos = gdalmetadata.find(datatypestring) #ppos = gdalmetadata.find('</p>', # datatypepos + len(datatypestring)) #datatypedesc = gdalmetadata[datatypepos + # len(datatypestring):ppos] #shortdesc = datatypedesc.split()[0] #self.showInfo('GDAL data type: GDT_'+shortdesc) # Call the findGdalDatatype function #self.findGdalDatatype(shortdesc) # self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.calcHistPushButton.setEnabled(True) self.suggestlevelsPushButton.setEnabled(True) def bandChanged(self): band = self.bandComboBox.currentIndex() + 1 self.showInfo("Band changed: " + str(band)) statistics = self.inputrasterprovider.bandStatistics(band) #self.showInfo("Band statistics: " + str(statistics.minimumValue) + # " - " + str(statistics.maximumValue) + # " - " + str(statistics.mean)) self.bandmin = statistics.minimumValue self.bandmax = statistics.maximumValue dt = self.inputrasterprovider.dataType(band) # Integer data type if (dt == Qgis.Byte or dt == Qgis.UInt16 or dt == Qgis.Int16 or dt == Qgis.UInt32 or dt == Qgis.Int32): self.intband = True self.minValueSpinBox.setDecimals(0) self.maxValueSpinBox.setDecimals(0) self.levelSpinBox.setDecimals(0) self.bandMinLabel.setText(str(int(statistics.minimumValue))) self.bandMaxLabel.setText(str(int(statistics.maximumValue))) else: self.intband = False self.minValueSpinBox.setDecimals(5) self.maxValueSpinBox.setDecimals(5) self.levelSpinBox.setDecimals(5) minlabtext = "{0:.5f}".format(statistics.minimumValue) self.bandMinLabel.setText(minlabtext) maxlabtext = "{0:.5f}".format(statistics.maximumValue) self.bandMaxLabel.setText(maxlabtext) #self.minValueSpinBox.setMinimum(statistics.minimumValue) self.maxValueSpinBox.setMinimum(statistics.minimumValue) #self.minValueSpinBox.setMaximum(statistics.maximumValue) self.maxValueSpinBox.setMaximum(statistics.maximumValue) #self.minValueSpinBox.setValue(statistics.minimumValue) if not (statistics.statsGathered & statistics.Mean): bandmean = (statistics.minimumValue + statistics.maximumValue) / 2 else: #self.showInfo("statsgathered: " + str(statistics.statsGathered)) bandmean = statistics.mean if self.intband: self.minValueSpinBox.setValue(int(ceil(bandmean))) else: self.minValueSpinBox.setValue(bandmean) self.maxValueSpinBox.setValue(statistics.maximumValue) self.histMinValue.setText(str(statistics.minimumValue)) self.histMaxValue.setText(str(statistics.maximumValue)) self.levelSpinBox.setMinimum(statistics.minimumValue) self.levelSpinBox.setMaximum(statistics.maximumValue) self.histogramAvailable = False #if self.inputrasterprovider.hasStatistics(band): #if statistics.statsGathered: #histogram = statistics.histogramVector #self.showInfo("Histogram: " + str(histogram)) #range = min to max #np.histogram(band, 50, range) def minmaxvalueChanged(self): #if self.minValueSpinBox is None: # return minvalue = self.minValueSpinBox.value() #if minvalue is None: # return #if self.maxValueSpinBox is None: # return maxvalue = self.maxValueSpinBox.value() #if maxvalue is None: # return if isnan(maxvalue) or isnan(minvalue): return self.showInfo("minvalue: " + str(minvalue) + " Maxvalue: " + str(maxvalue)) #if self.intband: # minvalue = int(minvalue) # maxvalue = int(maxvalue) if abs(maxvalue - minvalue) < 0.00001: #if maxvalue == maxvalue: self.calcHistPushButton.setEnabled(False) else: self.calcHistPushButton.setEnabled(True) # Update the min and max value spinboxes self.minValueSpinBox.setMaximum(maxvalue) self.maxValueSpinBox.setMinimum(minvalue) self.minValueSpinBox.setMinimum(self.bandmin) def minmaxvalueEdFinished(self): minvalue = self.minValueSpinBox.value() maxvalue = self.maxValueSpinBox.value() if self.intband: minvalue = int(minvalue) maxvalue = int(maxvalue) self.showInfo("minvalue: " + str(minvalue) + " Maxvalue: " + str(maxvalue)) # Update the spin box for adding levels self.levelSpinBox.setMinimum(minvalue) self.levelSpinBox.setMaximum(maxvalue) if self.levelSpinBox.value() < minvalue: self.levelSpinBox.setValue(minvalue) if self.levelSpinBox.value() > maxvalue: self.levelSpinBox.setValue(maxvalue) # Update the min and max value spinboxes self.minValueSpinBox.setMaximum(maxvalue) self.maxValueSpinBox.setMinimum(minvalue) # Adjust the levels: i = 0 while self.levelsListView.model().item(i): #for i in range(self.levelsListView.model().rowCount()): #self.showInfo("Element: " + # str(self.levelsListView.model().item(i).text())) #continue value = float(self.levelsListView.model().item(i).text()) if value < minvalue: if i == 0: self.levelsListView.model().item(i).setText(str(minvalue)) i = i + 1 else: self.levelsListView.model().removeRow(i) elif value > maxvalue: if i == self.levelsListView.model().rowCount() - 1: self.levelsListView.model().item(i).setText(str(maxvalue)) i = i + 1 else: self.levelsListView.model().removeRow(i) else: i = i + 1 self.drawHistogram() def calculateHistogram(self): self.showInfo("Calculating histogram...") if self.inputlayer is None: return self.showInfo("Calculating histogram...") # Check if there is only one value myrange = (self.minValueSpinBox.value(), self.maxValueSpinBox.value()) self.inputextent = self.inputlayer.extent() self.inputrdp = self.inputlayer.dataProvider() width = self.inputlayer.width() height = self.inputlayer.height() if width == 0 or height == 0: self.showInfo("Image has zero width or height") return extwidth = self.inputextent.width() extheight = self.inputextent.height() # Read the raster block and get the maximum value rasterblock = self.inputrdp.block(1, self.inputextent, width, height) # Create a numpy array version of the image imageMat = np.zeros((height, width), dtype=np.float16) # This one takes a lot of time! for row in range(height): for column in range(width): imageMat[row, column] = rasterblock.value(row, column) self.showInfo("Image: " + str(height) + ", " + str(width) + " - " + str(imageMat[row, column])) self.histo = np.histogram(imageMat, self.histobins, myrange) #relevantpixels = imageMat[np.where(imageMat >= bandval)] minlevel = float(self.bandMinLabel.text()) relevantpixels = imageMat[np.where(imageMat >= minlevel)] #self.showInfo("Histogram: " + str(self.histo)) nanpercentage = 100.0 - 100.0 * len(relevantpixels) / (width * height) self.bandNANLabel.setText("{0:.1f}".format(nanpercentage)) #self.showInfo("Percentage NAN: " + str(100.0 - 100.0 * # len(relevantpixels) / (width * height))) #self.showInfo("First element: " + str(self.histo[0])) #self.showInfo("First element, first: " + str(self.histo[0][0])) #self.showInfo("First element, second: " + str(self.histo[0][1])) self.histMinValue.setText(str(self.minValueSpinBox.value())) self.histMaxValue.setText(str(self.maxValueSpinBox.value())) if self.intband: self.histMinValue.setText(str(int(self.minValueSpinBox.value()))) self.histMaxValue.setText(str(int(self.maxValueSpinBox.value()))) self.histogramAvailable = True self.drawHistogram() def drawHistogram(self): #if self.inputlayer is None: # return self.showInfo("Drawing histogram...") viewprect = QRectF(self.histoGraphicsView.viewport().rect()) self.histoGraphicsView.setSceneRect(viewprect) self.setupScene.clear() self.setupScene.update() histbottom = self.histoGraphicsView.sceneRect().bottom() histtop = self.histoGraphicsView.sceneRect().top() left = self.histoGraphicsView.sceneRect().left() + self.histopadding right = self.histoGraphicsView.sceneRect().right() - self.histopadding histheight = histbottom - histtop histwidth = right - left step = 1.0 * histwidth / self.histobins maxlength = histheight padding = 1 ll = QPoint(self.histopadding - 1, histheight - padding) start = QPointF(self.histoGraphicsView.mapToScene(ll)) # Check if there is only one value #myrange = (self.minValueSpinBox.value(),self.maxValueSpinBox.value()) if self.histogramAvailable: maxvalue = 0.0 for i in range(len(self.histo[0])): if self.histo[0][i] > maxvalue: maxvalue = self.histo[0][i] if maxvalue == 0: return self.maxBinNumber.setText(str(maxvalue)) # Create the histogram: #self.showInfo("maxvalue: " + str(maxvalue)) #self.showInfo("maxlength: " + str(maxlength)) #self.showInfo("step: " + str(step)) for i in range(self.histobins): binnumber = self.histo[0][i] if binnumber == 0: continue height = (1.0 * self.histo[0][i] / maxvalue * (maxlength - padding)) rectangle = QGraphicsRectItem(start.x() + step * i, start.y(), step, -height) rectangle.setPen(QPen(QColor(102, 102, 102))) rectangle.setBrush(QBrush(QColor(240, 240, 240))) self.setupScene.addItem(rectangle) #self.showInfo(str(i) + ": " + str(height)) #if self.levelsListView.model().rowCount() > 0: # Add lines for the levels minvalue = float(self.histMinValue.text()) maxvalue = float(self.histMaxValue.text()) datarange = maxvalue - minvalue if datarange == 0: return i = 0 while self.levelsListView.model().item(i): #self.showInfo("Element: " + # str(self.levelsListView.model().item(i).text())) #continue value = float(self.levelsListView.model().item(i).text()) xvalue = start.x() + histwidth * (value - minvalue) / datarange line = QGraphicsLineItem(xvalue, 0, xvalue, histheight) if i == 0 or i == (self.levelsListView.model().rowCount() - 1): line.setPen(QPen(QColor(204, 0, 0))) else: line.setPen(QPen(QColor(0, 204, 0))) self.setupScene.addItem(line) i = i + 1 def suggestLevels(self): self.listModel.clear() self.showInfo("Suggesting levels") levels = self.levelsSpinBox.value() startvalue = self.minValueSpinBox.value() endvalue = self.maxValueSpinBox.value() increment = (endvalue - startvalue) / levels for i in range(levels + 1): value = startvalue + increment * i if self.intband: value = int(value) item = QStandardItem(str(value)) self.listModel.appendRow(item) self.drawHistogram() def addLevel(self): newvalue = self.levelSpinBox.value() if self.intband: newvalue = int(newvalue) for i in range(self.listModel.rowCount()): # Check if the value is already in the list if self.listModel.item(i).text() == str(newvalue): return else: # Maintain a sorted list of distances if (float(self.listModel.item(i).text()) > float(str(newvalue))): item = QStandardItem(str(newvalue)) self.listModel.insertRow(i, item) self.drawHistogram() return item = QStandardItem(str(newvalue)) self.listModel.appendRow(item) #if self.histogramAvailable: # addLevelsToHistogram() self.drawHistogram() def removeLevel(self): self.levelsListView.setUpdatesEnabled(False) indexes = self.levelsListView.selectedIndexes() indexes.sort() for i in range(len(indexes) - 1, -1, -1): self.listModel.removeRow(indexes[i].row()) self.levelsListView.setUpdatesEnabled(True) #if self.histogramAvailable: # removeLevelFromHistogram() self.drawHistogram() def layerlistchanged(self): self.layerlistchanging = True self.showInfo("Layer list changed") # Repopulate the input layer combo box # Save the currently selected input layer inputlayerid = self.inputlayerid self.inputRaster.clear() for alayer in self.iface.legendInterface().layers(): if alayer.type() == QgsMapLayer.RasterLayer: gdalmetadata = alayer.metadata() # Skip WMS layers WMSstring = 'Web Map Service' wmspos = gdalmetadata.find(WMSstring) if wmspos != -1: continue self.inputRaster.addItem(alayer.name(), alayer.id()) # Set the previous selection for i in range(self.inputRaster.count()): if self.inputRaster.itemData(i) == inputlayerid: self.inputRaster.setCurrentIndex(i) self.layerlistchanging = False #self.updateui() def updateui(self): """Do the necessary updates after a layer selection has been changed.""" #if self.layerlistchanged: # return #self.outputRaster.setText(self.inputRaster.currentText() + # '_' + 'thinned') layerindex = self.inputRaster.currentIndex() layerId = self.inputRaster.itemData(layerindex) #inputlayer = QgsMapLayerRegistry.instance().mapLayer(layerId) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is not None: pass else: pass def findGdalDatatype(self, shortdesc): gdaldatatype = None # // Unknown or unspecified type # GDT_Unknown = GDALDataType(C.GDT_Unknown) if shortdesc == 'Unknown': gdaldatatype = gdal.GDT_Unknown # // Eight bit unsigned integer # GDT_Byte = GDALDataType(C.GDT_Byte) elif shortdesc == 'Byte': gdaldatatype = gdal.GDT_Byte # // Sixteen bit unsigned integer # GDT_UInt16 = GDALDataType(C.GDT_UInt16) elif shortdesc == 'UInt16': gdaldatatype = gdal.GDT_UInt16 # // Sixteen bit signed integer # GDT_Int16 = GDALDataType(C.GDT_Int16) elif shortdesc == 'Int16': gdaldatatype = gdal.GDT_Int16 # // Thirty two bit unsigned integer # GDT_UInt32 = GDALDataType(C.GDT_UInt32) elif shortdesc == 'UInt32': gdaldatatype = gdal.GDT_UInt32 # // Thirty two bit signed integer # GDT_Int32 = GDALDataType(C.GDT_Int32) elif shortdesc == 'Int32': gdaldatatype = gdal.GDT_Int32 # // Thirty two bit floating point # GDT_Float32 = GDALDataType(C.GDT_Float32) elif shortdesc == 'Float32': gdaldatatype = gdal.GDT_Float32 # // Sixty four bit floating point # GDT_Float64 = GDALDataType(C.GDT_Float64) elif shortdesc == 'Float64': gdaldatatype = gdal.GDT_Float64 # // Complex Int16 # GDT_CInt16 = GDALDataType(C.GDT_CInt16) elif shortdesc == 'CInt16': gdaldatatype = gdal.CInt16 # // Complex Int32 # GDT_CInt32 = GDALDataType(C.GDT_CInt32) elif shortdesc == 'CInt32': gdaldatatype = gdal.CInt32 # // Complex Float32 # GDT_CFloat32 = GDALDataType(C.GDT_CFloat32) elif shortdesc == 'CFloat32': gdaldatatype = gdal.CFloat32 # // Complex Float64 # GDT_CFloat64 = GDALDataType(C.GDT_CFloat64) elif shortdesc == 'CFloat64': gdaldatatype = gdal.CFloat64 # // maximum type # + 1 # GDT_TypeCount = GDALDataType(C.GDT_TypeCount) elif shortdesc == 'TypeCount': gdaldatatype = gdal.TypeCount self.gdaldatatype = gdaldatatype def killWorker(self): """Kill the worker thread.""" if self.worker is not None: QgsMessageLog.logMessage(self.tr('Killing worker'), self.THINGREYSCALE, Qgis.Info) self.worker.kill() def showError(self, text): """Show an error.""" self.iface.messageBar().pushMessage(self.tr('Error'), text, level=QgsMessageBar.CRITICAL, duration=3) QgsMessageLog.logMessage('Error: ' + text, self.THINGREYSCALE, QgsMessageLog.CRITICAL) def showWarning(self, text): """Show a warning.""" self.iface.messageBar().pushMessage(self.tr('Warning'), text, level=QgsMessageBar.WARNING, duration=2) QgsMessageLog.logMessage('Warning: ' + text, self.THINGREYSCALE, QgsMessageLog.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.THINGREYSCALE, Qgis.Info) # def help(self): # #QDesktopServices.openUrl(QUrl.fromLocalFile(self.plugin_dir + # "/help/build/html/index.html")) # QDesktopServices.openUrl(QUrl.fromLocalFile(self.plugin_dir + # "/help/index.html")) # #showPluginHelp() 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('ThinGreyScaleDialog', message) def browse(self): settings = QSettings() key = '/UI/lastShapefileDir' outDir = settings.value(key) home = outDir #outDir = expanduser("~") #filter = (self.DEFAULTPROVIDER + " (*" + # self.DEFAULTEXTENSION + ");;All files (*)") filter = (self.DEFAULTPROVIDER + " (*" + self.DEFAULTEXTENSION + self.EXTRAEXTENSION + ")") #if (self.gdalprovider != self.DEFAULTPROVIDER and # (self.canCreateCopy or # self.canCreate)): # filter = (self.gdalprovider + " (*" + self.gdalext + # ");;" + filter) outFilePath = QFileDialog.getSaveFileName(self, 'Specify file name for skeleton', outDir, filter) outFilePath = unicode(outFilePath) if outFilePath: root, ext = splitext(outFilePath) if ext.lower() != '.tif' and ext.lower() != '.tiff': outFilePath = '%s.tif' % outFilePath outDir = dirname(outFilePath) settings.setValue(key, outDir) # (self.canCreateCopy or self.canCreate): # fileName = splitext(str(fileName))[0]+self.gdalext self.outputRaster.setText(outFilePath) # Overriding def resizeEvent(self, event): #self.showInfo("resizeEvent") self.calculateHistogram() def help(self): #QDesktopServices.openUrl(QUrl.fromLocalFile( # self.plugin_dir + "/help/html/index.html")) showPluginHelp(None, "help/html/index") # 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 UploadOverview(uicls_log, basecls_log): """Upload status overview dialog.""" MAX_SCHEMATISATION_MODELS = 3 def __init__(self, plugin_dock, parent=None): super().__init__(parent) self.setupUi(self) self.plugin_dock = plugin_dock self.threedi_api = self.plugin_dock.threedi_api self.tc = ThreediCalls(self.plugin_dock.threedi_api) self.communication = self.plugin_dock.communication self.feedback_logger = ListViewLogger(self.lv_upload_feedback) self.upload_thread_pool = QThreadPool() self.ended_tasks = OrderedDict() self.upload_progresses = defaultdict(lambda: ("NO TASK", 0.0, 0.0)) self.current_upload_row = 0 self.schematisation = None self.schematisation_sqlite = None self.schematisation_id = None self.pb_new_upload.clicked.connect(self.upload_new_model) self.pb_hide.clicked.connect(self.close) self.tv_model = None self.setup_view_model() self.adjustSize() @property def current_local_schematisation(self): """Return currently loaded local schematisation.""" return self.plugin_dock.current_local_schematisation def setup_view_model(self): """Setting up model and columns for TreeView.""" nr_of_columns = 4 self.tv_model = QStandardItemModel(0, nr_of_columns - 1) self.tv_model.setHorizontalHeaderLabels(["Schematisation name", "Revision", "Commit message", "Status"]) self.tv_uploads.setModel(self.tv_model) self.tv_uploads.selectionModel().selectionChanged.connect(self.on_upload_context_change) for i in range(nr_of_columns): self.tv_uploads.resizeColumnToContents(i) self.progress_widget.hide() self.label_success.hide() self.label_failure.hide() def on_upload_context_change(self): """Updating progress bars based on upload selection change.""" selected_indexes = self.tv_uploads.selectedIndexes() if selected_indexes: current_index = selected_indexes[0] current_row = current_index.row() self.current_upload_row = current_row + 1 self.on_update_upload_progress(self.current_upload_row, *self.upload_progresses[self.current_upload_row]) self.feedback_logger.clear() try: for msg, success in self.ended_tasks[self.current_upload_row]: if success: self.feedback_logger.log_info(msg) else: self.feedback_logger.log_error(msg) except KeyError: pass status_item = self.tv_model.item(current_row, 3) status = status_item.text() if status == UploadStatus.SUCCESS.value: self.progress_widget.hide() self.label_success.show() self.label_failure.hide() elif status == UploadStatus.FAILURE.value: self.progress_widget.hide() self.label_success.hide() self.label_failure.show() else: self.progress_widget.show() self.label_success.hide() self.label_failure.hide() def add_upload_to_model(self, upload_specification): """Initializing a new upload.""" create_revision = upload_specification["create_revision"] schematisation = upload_specification["schematisation"] schema_name_item = QStandardItem(f"{schematisation.name}") revision = upload_specification["latest_revision"] revision_number = revision.number + 1 if create_revision is True else revision.number revision_item = QStandardItem(f"{revision_number}") commit_msg_item = QStandardItem(f"{upload_specification['commit_message']}") status_item = QStandardItem(UploadStatus.IN_PROGRESS.value) self.tv_model.appendRow([schema_name_item, revision_item, commit_msg_item, status_item]) upload_row_number = self.tv_model.rowCount() upload_row_idx = self.tv_model.index(upload_row_number - 1, 0) self.tv_uploads.selectionModel().setCurrentIndex(upload_row_idx, QItemSelectionModel.ClearAndSelect) worker = UploadProgressWorker( self.threedi_api, self.current_local_schematisation, upload_specification, upload_row_number ) worker.signals.upload_progress.connect(self.on_update_upload_progress) worker.signals.thread_finished.connect(self.on_upload_finished_success) worker.signals.upload_failed.connect(self.on_upload_failed) worker.signals.revision_committed.connect(self.on_revision_committed) self.upload_thread_pool.start(worker) def upload_new_model(self): """Initializing new upload wizard.""" if not self.current_local_schematisation or not self.current_local_schematisation.sqlite: warn_msg = "Please load the schematisation first before starting the upload." self.communication.show_warn(warn_msg, parent=self) self.plugin_dock.build_options.load_local_schematisation() return self.schematisation_sqlite = self.current_local_schematisation.sqlite schema_sqlite_loaded = is_toolbox_spatialite_loaded(self.schematisation_sqlite) if schema_sqlite_loaded is False: title = "Warning" question = ( "Warning: the Spatialite that you loaded with the 3Di Toolbox is not in the revision you are " "about to upload. Do you want to continue?" ) on_continue_answer = self.communication.ask(self, title, question, QMessageBox.Warning) if on_continue_answer is not True: return self.schematisation_id = self.current_local_schematisation.id self.schematisation = self.tc.fetch_schematisation(self.schematisation_id) current_wip_revision = self.current_local_schematisation.wip_revision latest_revision = ( self.tc.fetch_schematisation_latest_revision(self.schematisation_id) if current_wip_revision.number > 0 else None ) latest_revision_number = latest_revision.number if latest_revision else 0 if latest_revision_number != current_wip_revision.number: question = f"WIP revision number different than latest online revision ({latest_revision_number})" answer = self.communication.custom_ask(self, "Pick action", question, "Upload anyway?", "Cancel") if answer == "Cancel": return upload_wizard_dialog = UploadWizard(self.plugin_dock, self) upload_wizard_dialog.exec_() new_upload = upload_wizard_dialog.new_upload if not new_upload: return if not new_upload["upload_only"]: deletion_dlg = ModelDeletionDialog(self.plugin_dock, self) if len(deletion_dlg.threedi_models) >= self.MAX_SCHEMATISATION_MODELS: deletion_dlg.exec_() if len(deletion_dlg.threedi_models) >= self.MAX_SCHEMATISATION_MODELS: self.communication.bar_warn("Uploading canceled...") return self.add_upload_to_model(new_upload) def on_revision_committed(self): """Handling actions on successful revision commit.""" self.plugin_dock.update_schematisation_view() def on_update_upload_progress(self, upload_row_number, task_name, task_progress, total_progress): """Handling actions on upload progress update.""" self.upload_progresses[upload_row_number] = (task_name, task_progress, total_progress) if self.current_upload_row == upload_row_number: self.lbl_current_task.setText(task_name) self.pbar_current_task.setValue(task_progress) self.pbar_total_upload.setValue(total_progress) if task_progress == 100.0 and task_name != "DONE": success = True enriched_success_message = f"{task_name} ==> done" ended_task_row = (enriched_success_message, success) if upload_row_number not in self.ended_tasks: self.ended_tasks[upload_row_number] = [ended_task_row] else: upload_ended_tasks = self.ended_tasks[upload_row_number] if ended_task_row not in upload_ended_tasks: upload_ended_tasks.append(ended_task_row) else: return self.feedback_logger.log_info(enriched_success_message) def on_upload_finished_success(self, upload_row_number, msg): """Handling action on upload success.""" item = self.tv_model.item(upload_row_number - 1, 3) item.setText(UploadStatus.SUCCESS.value) self.plugin_dock.communication.bar_info(msg, log_text_color=Qt.darkGreen) self.on_upload_context_change() def on_upload_failed(self, upload_row_number, error_message): """Handling action on upload failure.""" item = self.tv_model.item(upload_row_number - 1, 3) item.setText(UploadStatus.FAILURE.value) self.plugin_dock.communication.bar_error(error_message, log_text_color=Qt.red) success = False failed_task_name = self.upload_progresses[self.current_upload_row][0] enriched_error_message = f"{failed_task_name} ==> failed\n{error_message}" failed_task_row = (enriched_error_message, success) if upload_row_number not in self.ended_tasks: self.ended_tasks[upload_row_number] = [failed_task_row] else: self.ended_tasks[upload_row_number].append(failed_task_row) self.feedback_logger.log_error(enriched_error_message) self.on_upload_context_change()
class DialogExportData(QDialog, DIALOG_UI): ValidExtensions = ['xtf', 'itf', 'gml', 'xml'] current_row_schema = 0 def __init__(self, iface, db, qgis_utils): QDialog.__init__(self) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.iface = iface self.db = db self.qgis_utils = qgis_utils self.base_configuration = BaseConfiguration() self.ilicache = IliCache(self.base_configuration) self.ilicache.refresh() self.type_combo_box.clear() self.type_combo_box.addItem( QCoreApplication.translate("DialogExportData", "PostgreSQL/PostGIS"), 'pg') self.type_combo_box.addItem( QCoreApplication.translate("DialogExportData", "GeoPackage"), 'gpkg') self.type_combo_box.currentIndexChanged.connect(self.type_changed) self.type_changed() self.xtf_file_browse_button.clicked.connect( make_save_file_selector( self.xtf_file_line_edit, title=QCoreApplication.translate("DialogExportData", "Save in XTF Transfer File"), file_filter=QCoreApplication.translate( "DialogExportData", "XTF Transfer File (*.xtf);;Interlis 1 Transfer File (*.itf);;XML (*.xml);;GML (*.gml)" ), extension='.xtf', extensions=['.' + ext for ext in self.ValidExtensions])) self.xtf_file_browse_button.clicked.connect( self.xtf_browser_opened_to_true) self.xtf_browser_was_opened = False self.validators = Validators() fileValidator = FileValidator( pattern=['*.' + ext for ext in self.ValidExtensions], allow_non_existing=True) self.xtf_file_line_edit.setPlaceholderText( QCoreApplication.translate("DialogExportData", "[Name of the XTF to be created]")) self.xtf_file_line_edit.setValidator(fileValidator) self.xtf_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.xtf_file_line_edit.textChanged.connect( self.xtf_browser_opened_to_false) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) # PG self.db_connect_label.setToolTip(self.db.get_display_conn_string()) self.db_connect_label.setText(self.db.dict_conn_params["database"]) self.connection_setting_button.clicked.connect(self.show_settings) self.connection_setting_button.setText( QCoreApplication.translate("DialogExportData", "Connection Settings")) # GPKG self.gpkg_file_line_edit.setPlaceholderText( QCoreApplication.translate( "DialogExportData", "[Name of the Geopackage to be created]")) self.gpkg_file_browse_button.clicked.connect( make_file_selector(self.gpkg_file_line_edit, title=QCoreApplication.translate( "DialogExportData", "Open GeoPackage database file"), file_filter=QCoreApplication.translate( "DialogExportData", "GeoPackage Database (*.gpkg)"))) gpkgFileValidator = FileValidator(pattern='*.gpkg') self.gpkg_file_line_edit.setValidator(gpkgFileValidator) self.gpkg_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.gpkg_file_line_edit.textChanged.emit( self.gpkg_file_line_edit.text()) # LOG self.log_config.setTitle( QCoreApplication.translate("DialogExportData", "Show log")) self.log_config.setFlat(True) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self.buttonBox.addButton( QCoreApplication.translate("DialogExportData", "Export data"), QDialogButtonBox.AcceptRole) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.show_help) def showEvent(self, event): # update after create dialog self.update_schema_names_model() self.update_model_names(self.get_checked_schema()) self.restore_configuration() def update_schema_names_model(self): res, msg = self.db.test_connection() schema_names = self.db._schema_names_list() if schema_names: for schema_name in schema_names: item = QListWidgetItem(schema_name['schema_name']) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setCheckState(Qt.Unchecked) self.schema_names_list_widget.addItem(item) default_item = self.schema_names_list_widget.item(0) default_item.setCheckState(Qt.Checked) else: self.schema_names_list_widget.clear() self.export_models_list_view.setModel(QStandardItemModel()) message_error = 'There are no schemes to export into the database. Please select another database.' self.txtStdout.setText( QCoreApplication.translate("DialogExportData", message_error)) self.show_message(message_error, Qgis.Warning) self.schema_names_list_widget.currentRowChanged.connect( self.on_current_row_changed_schema_names) self.schema_names_list_widget.itemChanged.connect( self.on_itemchanged_schema_name) def on_itemchanged_schema_name(self, selected_item): # disconnect signal to do changes in the items self.schema_names_list_widget.itemChanged.disconnect( self.on_itemchanged_schema_name) for index in range(self.schema_names_list_widget.count()): item = self.schema_names_list_widget.item(index) item.setCheckState(Qt.Unchecked) item.setSelected(False) if item == selected_item: select_index = index item = self.schema_names_list_widget.item(select_index) item.setCheckState(Qt.Checked) item.setSelected(True) # Update list view with models name info schema_name = item.text() self.update_model_names(schema_name) # connect signal to check when the items change self.schema_names_list_widget.itemChanged.connect( self.on_itemchanged_schema_name) def on_current_row_changed_schema_names(self, current_row): for index in range(self.schema_names_list_widget.count()): item = self.schema_names_list_widget.item(index) item.setCheckState(Qt.Unchecked) item = self.schema_names_list_widget.item(current_row) if item: item.setCheckState(Qt.Checked) # Update list view with models name info schema_name = item.text() self.update_model_names(schema_name) def update_model_names(self, dbschema): self.export_models_qmodel = QStandardItemModel() db_models = None if self.type_combo_box.currentData() == 'gpkg': db_models = self.db.get_models() elif self.type_combo_box.currentData() == 'pg': db_models = self.db.get_models(dbschema) if dbschema else None if db_models: for db_model in db_models: regex = re.compile(r'(?:\{[^\}]*\}|\s)') for modelname in regex.split(db_model['modelname']): if modelname and modelname not in DEFAULT_HIDDEN_MODELS: item = QStandardItem(modelname.strip()) item.setCheckable(False) item.setEditable(False) self.export_models_qmodel.appendRow(item) self.export_models_list_view.setModel(self.export_models_qmodel) def get_checked_schema(self): checked_schema = None for index in range(self.schema_names_list_widget.count()): item = self.schema_names_list_widget.item(index) if item.checkState() == Qt.Checked: checked_schema = item.text() break return checked_schema def get_ili_models(self): ili_models = list() for index in range(self.export_models_qmodel.rowCount()): item = self.export_models_qmodel.item(index) ili_models.append(item.text()) return ili_models def show_settings(self): dlg = self.qgis_utils.get_settings_dialog() dlg.tabWidget.setCurrentIndex(SETTINGS_CONNECTION_TAB_INDEX) if dlg.exec_(): self.db = self.qgis_utils.get_db_connection() self.db_connect_label.setToolTip(self.db.get_display_conn_string()) self.db_connect_label.setText(self.db.dict_conn_params['database']) self.update_schema_names_model() def accepted(self): configuration = self.update_configuration() if not self.get_checked_schema(): message_error = QCoreApplication.translate( "DialogExportData", "You need to select a valid schema where to get the data from." ) self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.connection_setting_button.setFocus() return if not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable: message_error = QCoreApplication.translate( "DialogExportData", "Please set a valid XTF file before exporting data.") self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.xtf_file_line_edit.setFocus() return if not self.get_ili_models(): message_error = QCoreApplication.translate( "DialogExportData", "Please set a valid schema to export. This schema does not have information to export." ) self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.export_models_list_view.setFocus() return if not configuration.iliexportmodels: message_error = QCoreApplication.translate( "DialogExportData", "Please set a model before exporting data.") self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.export_models_list_view.setFocus() return if self.type_combo_box.currentData() == 'gpkg': if not configuration.dbfile or self.gpkg_file_line_edit.validator( ).validate(configuration.dbfile, 0)[0] != QValidator.Acceptable: message_error = QCoreApplication.translate( "DialogExportData", "Please set an existing database file before creating the project." ) self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.gpkg_file_line_edit.setFocus() return # If xtf browser was opened and the file exists, the user already chose # to overwrite the file if os.path.isfile(self.xtf_file_line_edit.text().strip() ) and not self.xtf_browser_was_opened: self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.msg.setText( QCoreApplication.translate( "DialogExportData", "{filename} already exists.\nDo you want to replace it?"). format(filename=os.path.basename( self.xtf_file_line_edit.text().strip()))) self.msg.setWindowTitle( QCoreApplication.translate("DialogExportData", "Save in XTF Transfer File")) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box = self.msg.exec_() if msg_box == QMessageBox.No: return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.progress_bar.setValue(0) self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() exporter = iliexporter.Exporter() exporter.tool_name = 'ili2pg' if self.type_combo_box.currentData( ) == 'pg' else 'ili2gpkg' exporter.configuration = configuration self.save_configuration(configuration) exporter.stdout.connect(self.print_info) exporter.stderr.connect(self.on_stderr) exporter.process_started.connect(self.on_process_started) exporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if exporter.run() != iliexporter.Exporter.SUCCESS: self.enable() self.progress_bar.hide() self.show_message( QCoreApplication.translate( "DialogExportData", "An error occurred when exporting the data. For more information see the log..." ), Qgis.Warning) return except JavaNotFoundError: # Set JAVA PATH get_java_path_dlg = DialogGetJavaPath() get_java_path_dlg.setModal(True) if get_java_path_dlg.exec_(): configuration = self.update_configuration() if not get_java_path_from_qgis_model_baker(): message_error_java = QCoreApplication.translate( "DialogExportData", """Java could not be found. You can configure the JAVA_HOME environment variable, restart QGIS and try again.""" ) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(message_error_java) self.show_message(message_error_java, Qgis.Warning) self.enable() self.progress_bar.hide() return self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) self.show_message( QCoreApplication.translate( "DialogExportData", "Export of the data was successfully completed."), Qgis.Success) def save_configuration(self, configuration): settings = QSettings() settings.setValue( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_export', configuration.xtffile) settings.setValue('Asistente-LADM_COL/db_connection_source', self.type_combo_box.currentData()) settings.setValue('Asistente-LADM_COL/QgisModelBaker/show_log', not self.log_config.isCollapsed()) if self.type_combo_box.currentData() == 'gpkg': settings.setValue( 'Asistente-LADM_COL/QgisModelBaker/ili2gpkg/dbfile', configuration.dbfile) def restore_configuration(self): settings = QSettings() self.xtf_file_line_edit.setText( settings.value( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_export')) self.type_combo_box.setCurrentIndex( self.type_combo_box.findData( settings.value('Asistente-LADM_COL/db_connection_source', 'pg'))) self.type_changed() # Show log value_show_log = settings.value( 'Asistente-LADM_COL/QgisModelBaker/show_log', False, type=bool) self.log_config.setCollapsed(not value_show_log) # set model repository # if there is no option by default use online model repository custom_model_is_checked = settings.value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', type=bool) if custom_model_is_checked: self.custom_model_directories = settings.value( 'Asistente-LADM_COL/models/custom_models') def update_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ configuration = ExportConfiguration() if self.type_combo_box.currentData() == 'pg': # PostgreSQL specific options configuration.dbhost = self.db.dict_conn_params["host"] configuration.dbport = self.db.dict_conn_params["port"] configuration.dbusr = self.db.dict_conn_params["username"] configuration.database = self.db.dict_conn_params["database"] configuration.dbschema = self.get_checked_schema() configuration.dbpwd = self.db.dict_conn_params["password"] elif self.type_combo_box.currentData() == 'gpkg': configuration.dbfile = self.db.dict_conn_params["dbfile"] configuration.xtffile = self.xtf_file_line_edit.text().strip() java_path = get_java_path_from_qgis_model_baker() if java_path: self.base_configuration.java_path = java_path # Check custom model directories if QSettings().value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', type=bool): if self.custom_model_directories is None: self.base_configuration.custom_model_directories_enabled = False else: self.base_configuration.custom_model_directories = self.custom_model_directories self.base_configuration.custom_model_directories_enabled = True configuration.base_configuration = self.base_configuration if self.get_ili_models(): configuration.iliexportmodels = ';'.join(self.get_ili_models()) configuration.ilimodels = ';'.join(self.get_ili_models()) return configuration def print_info(self, text, text_color='#000000', clear=False): self.txtStdout.setTextColor(QColor(text_color)) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = '#004905' if exit_code == 0 else '#aa2222' self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append( QCoreApplication.translate("DialogExportData", "Finished ({})").format(exit_code)) if result == iliexporter.Exporter.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: self.enable() def advance_progress_bar_by_text(self, text): if text.strip() == 'Info: compile models...': self.progress_bar.setValue(50) QCoreApplication.processEvents() elif text.strip() == 'Info: process data...': self.progress_bar.setValue(55) QCoreApplication.processEvents() elif text.strip() == 'Info: first validation pass...': self.progress_bar.setValue(70) QCoreApplication.processEvents() elif text.strip() == 'Info: second validation pass...': self.progress_bar.setValue(85) QCoreApplication.processEvents() def show_help(self): self.qgis_utils.show_help("export_data") def disable(self): self.type_combo_box.setEnabled(False) self.pg_config.setEnabled(False) self.ili_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.type_combo_box.setEnabled(True) self.pg_config.setEnabled(True) self.ili_config.setEnabled(True) self.buttonBox.setEnabled(True) def show_message(self, message, level): self.bar.pushMessage("Asistente LADM_COL", message, level, duration=0) def type_changed(self): self.progress_bar.hide() if self.type_combo_box.currentData() == 'pg': self.pg_config.show() self.gpkg_config.hide() elif self.type_combo_box.currentData() == 'gpkg': self.pg_config.hide() self.gpkg_config.show() def xtf_browser_opened_to_true(self): """ Slot. Sets a flag to true to eventually avoid asking a user whether to overwrite a file. """ self.xtf_browser_was_opened = True def xtf_browser_opened_to_false(self): """ Slot. Sets a flag to false to eventually ask a user whether to overwrite a file. """ self.xtf_browser_was_opened = False
class SchematisationLoad(uicls, basecls): """Dialog for local schematisation loading.""" def __init__(self, plugin_dock, parent=None): super().__init__(parent) self.setupUi(self) self.plugin_dock = plugin_dock self.working_dir = self.plugin_dock.plugin_settings.working_dir self.communication = self.plugin_dock.communication self.local_schematisations = list_local_schematisations( self.working_dir) self.tv_schematisations_model = QStandardItemModel() self.schematisations_tv.setModel(self.tv_schematisations_model) self.tv_revisions_model = QStandardItemModel() self.revisions_tv.setModel(self.tv_revisions_model) self.selected_local_schematisation = None self.pb_load.clicked.connect(self.load_local_schematisation) self.pb_cancel.clicked.connect(self.cancel_load_local_schematisation) self.schematisations_tv.selectionModel().selectionChanged.connect( self.populate_local_schematisation_revisions) self.revisions_tv.selectionModel().selectionChanged.connect( self.toggle_load_local_schematisation) self.populate_local_schematisations() def populate_local_schematisations(self): """Populate local schematisations.""" self.tv_revisions_model.clear() self.tv_schematisations_model.clear() header = ["Schematisation name", "Schematisation ID", "Absolute path"] self.tv_schematisations_model.setHorizontalHeaderLabels(header) for schematisation_id, local_schematisation in self.local_schematisations.items( ): name_item = QStandardItem(local_schematisation.name) name_item.setData(local_schematisation, role=Qt.UserRole) id_item = QStandardItem(str(schematisation_id)) dir_item = QStandardItem(local_schematisation.main_dir) self.tv_schematisations_model.appendRow( [name_item, id_item, dir_item]) for i in range(len(header)): self.schematisations_tv.resizeColumnToContents(i) def populate_local_schematisation_revisions(self): """Populate local schematisation revisions.""" self.tv_revisions_model.clear() header = ["Revision number", "Subdirectory"] self.tv_revisions_model.setHorizontalHeaderLabels(header) local_schematisation = self.get_selected_local_schematisation() wip_revision = local_schematisation.wip_revision if wip_revision is not None: number_item = QStandardItem(str(wip_revision.number)) number_item.setData(wip_revision, role=Qt.UserRole) subdir_item = QStandardItem(wip_revision.sub_dir) self.tv_revisions_model.appendRow([number_item, subdir_item]) for revision_number, local_revision in reversed( local_schematisation.revisions.items()): number_item = QStandardItem(str(revision_number)) number_item.setData(local_revision, role=Qt.UserRole) subdir_item = QStandardItem(local_revision.sub_dir) self.tv_revisions_model.appendRow([number_item, subdir_item]) for i in range(len(header)): self.schematisations_tv.resizeColumnToContents(i) if self.tv_revisions_model.rowCount() > 0: row_idx = self.tv_revisions_model.index(0, 0) self.revisions_tv.selectionModel().setCurrentIndex( row_idx, QItemSelectionModel.ClearAndSelect) self.toggle_load_local_schematisation() def toggle_load_local_schematisation(self): """Toggle load button if any schematisation revision is selected.""" selection_model = self.revisions_tv.selectionModel() if selection_model.hasSelection(): self.pb_load.setEnabled(True) else: self.pb_load.setDisabled(True) def get_selected_local_schematisation(self): """Get currently selected local schematisation.""" index = self.schematisations_tv.currentIndex() if index.isValid(): current_row = index.row() name_item = self.tv_schematisations_model.item(current_row, 0) local_schematisation = name_item.data(Qt.UserRole) else: local_schematisation = None return local_schematisation def get_selected_local_revision(self): """Get currently selected local revision.""" index = self.revisions_tv.currentIndex() if index.isValid(): current_row = index.row() name_item = self.tv_revisions_model.item(current_row, 0) local_revision = name_item.data(Qt.UserRole) else: local_revision = None return local_revision def load_local_schematisation(self): """Loading selected local schematisation.""" local_schematisation = self.get_selected_local_schematisation() local_revision = self.get_selected_local_revision() if not isinstance(local_revision, WIPRevision): title = "Pick action" question = f"Replace WIP with data from the revision {local_revision.number}?" picked_action_name = self.communication.custom_ask( self, title, question, "Replace", "Cancel") if picked_action_name == "Replace": wip_revision = local_schematisation.set_wip_revision( local_revision.number) replace_revision_data(local_revision, wip_revision) else: local_schematisation = None self.selected_local_schematisation = local_schematisation self.close() def cancel_load_local_schematisation(self): """Cancel local schematisation loading.""" self.close()
class AdjustmentDialogThresholds(QObject): COLOR_ERROR = QColor(224, 103, 103) COLOR_ATTENTION = QColor(237, 148, 76) COLOR_NEUTRAL = QColor(255, 255, 255) COLOR = {1: COLOR_NEUTRAL, 2: COLOR_ATTENTION, 3: COLOR_ERROR} sig_clickedRow = pyqtSignal(int) def __init__(self, parent, datasetSize): """ :type parent: gui.adjustmentDialog.AdjustmentDialog """ super().__init__() self.parent = parent self.tbl = self.parent.tableThresholds self.model = QStandardItemModel(datasetSize[0], datasetSize[1], self.tbl) self.initState = True self.thresholdExeeded = False self.tbl.setModel(self.model) self.tbl.resizeColumnsToContents() self.tbl.resizeRowsToContents() # Icons self.iconOk = QIcon() self.iconOk.addPixmap( QPixmap(":/plugins/SeilaplanPlugin/gui/icons/icon_green.png"), QIcon.Normal, QIcon.Off) self.iconErr = QIcon() self.iconErr.addPixmap( QPixmap( ":/plugins/SeilaplanPlugin/gui/icons/icon_exclamation.png"), QIcon.Normal, QIcon.Off) self.tbl.clicked.connect(self.onClick) def populate(self, header, dataset, valueColumn): self.model.setHorizontalHeaderLabels(header) self.tbl.hideColumn(5) # Insert data into cells for i, rowData in enumerate(dataset): for j, cellData in enumerate(rowData): if j == 0: # Create clickable info button in first column btnWidget = self.createInfoBtn(cellData) self.tbl.setIndexWidget(self.model.index(i, j), btnWidget) continue if j == 5 and isinstance(cellData, dict): loclen = len(cellData['loc']) if loclen > 0: # Set background color for cells where threshold is # exceeded color = self.COLOR[max(cellData['color'] or [1])] self.colorBackground(i, valueColumn, color) cellData = loclen item = QStandardItem(cellData) self.model.setItem(i, j, item) self.model.setData(self.model.index(i, j), cellData) # Adjust column widths self.tbl.resizeColumnsToContents() for idx in range(2, self.model.columnCount()): currSize = self.tbl.sizeHintForColumn(idx) self.tbl.setColumnWidth(idx, max(currSize, 100)) self.tbl.setFocusPolicy(Qt.NoFocus) self.updateTabIcon() def updateData(self, row, col, newVal): # Update background color of new values if col == 5 and isinstance(newVal, dict): locLen = len(newVal['loc']) color = self.COLOR[max(newVal['color'] or [1])] self.colorBackground(row, 4, color) newVal = locLen # Update value itself self.model.setData(self.model.index(row, col), newVal) self.updateTabIcon() # Remove the background color from initially calculated # cable line data if self.initState: self.initState = False for row in range(self.model.rowCount()): self.colorBackground(row, 3, self.COLOR_NEUTRAL) def colorBackground(self, row, col, color): self.model.setData(self.model.index(row, col), QBrush(color), Qt.BackgroundRole) def updateTabIcon(self): """ Updates icon of QTabWidget with an exclamation mark or check mark depending on presents of exceeded thresholds.""" thresholdExceeded = False for i in range(0, self.model.rowCount()): if i == 2: # Dont check thresholds for 'Sattelkraft' continue data = self.model.data(self.model.index(i, 5)) if data and data > 0: thresholdExceeded = True break if thresholdExceeded: self.parent.tabWidget.setTabIcon(2, self.iconErr) else: self.parent.tabWidget.setTabIcon(2, self.iconOk) def onClick(self, item): # Row is already selected if self.parent.selectedThdRow == item.row(): # Deselect self.tbl.clearSelection() # Emit select signal self.sig_clickedRow.emit(item.row()) def createInfoBtn(self, cellData): button = QPushButton('?') button.setMaximumSize(QSize(22, 22)) # Fill info text into message box button.clicked.connect( lambda: QMessageBox.information(self.parent, cellData[ 'title'], cellData['message'], QMessageBox.Ok)) cellWidget = QWidget() # Add layout to center button in cell layout = QHBoxLayout(cellWidget) layout.addWidget(button, 0, Qt.AlignCenter) layout.setAlignment(Qt.AlignCenter) cellWidget.setLayout(layout) return cellWidget
class DHIS2DataFetcher: """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 self.MSG_TITLE = self.tr('DHIS2 datafetcher') locale = QSettings().value('locale/userLocale')[0:2] self.info('Locale: "{}"'.format(locale)) locale_path = os.path.join( self.plugin_dir, 'i18n', '{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) else: self.info('Locale niet gevonden: "{}"'.format(locale_path)) # start a group/dir for all settings self.SETTINGS_GROUP = self.tr('dhis2datafetcher') QgsSettings().beginGroup(self.SETTINGS_GROUP, QgsSettings.Plugins) # Create the dialog (after translation) and keep reference self.dlg = DHIS2DataFetcherDialog(self.iface.mainWindow()) self.dlg.cb_ou.currentIndexChanged.connect(self.cb_ou_changed) self.dlg.cb_pe.currentIndexChanged.connect(self.cb_pe_changed) self.dlg.cb_dx.currentIndexChanged.connect(self.cb_dx_changed) self.dlg.cb_level.currentIndexChanged.connect(self.cb_level_changed) self.dlg.btn_load_geodata.clicked.connect(self.load_geodata_in_layer) self.dlg.btn_new_dataset.clicked.connect(self.new_dataset) # Replace the placeholder in the dialog with a QgsAuthConfigSelect widget self.dlg.cmb_profile_select.close() # this apparently also removes the widget?? self.dlg.cmb_profile_select = QgsAuthConfigSelect() self.dlg.gridLayout.addWidget(self.dlg.cmb_profile_select, 0, 1, 1, 1) # row, col, #rows, #cols self.dlg.cmb_profile_select.selectedConfigIdChanged.connect(self.selectAuthConfig) # Declare instance attributes self.actions = [] self.menu = QMenu(self.tr(u'KIT - DHIS2 Data Fetcher')) self.iface.pluginMenu().addMenu(self.menu) self.menu.setIcon(QIcon(':/plugins/DHIS2DataFetcher/icon_kit.png')) self.toolbar = self.iface.addToolBar(u'DHIS2DataFetcher') self.toolbar.setObjectName(u'DHIS2DataFetcher') self.api_url = None # url as defined by user in authorisation profile self.auth_id = None # authorisation id to be used in nam creation AND vectorlayer creation uri self.gui_inited = False self.nam = None # created in gui during authorisation profile choice self.ou_items = [] self.pe_items = [] self.dx_items = [] # current demo site self.analytics_url = 'https://play.dhis2.org/2.28/api/' self.username = '******' self.password = '******' self.level = 2 # connect to the iface.projectRead signal to be able to refresh data in a project with a dhis2 layer self.iface.projectRead.connect(self.update_dhis2_project) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu( self.tr(u'KIT - DHIS2 Data Fetcher'), action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar self.dlg = None QgsSettings().endGroup() self.iface.projectRead.disconnect(self.update_dhis2_project) def msg(self, msg=''): self.iface.messageBar().pushMessage(self.MSG_TITLE, msg) def info(self, msg=''): QgsMessageLog.logMessage('{}'.format(msg), self.MSG_TITLE, Qgis.Info) def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/DHIS2DataFetcher/icon_kit.png' self.add_action( icon_path, text=self.tr(u'KIT - Fetch DHIS2 Data'), callback=self.show_dialog, parent=self.iface.mainWindow()) # help menu icon_path = ':/plugins/DHIS2DataFetcher/icon_kit.png' self.add_action( icon_path, text=self.tr(u'Code and (preliminary) Documentation'), callback=self.show_help, add_to_toolbar=False, parent=self.iface.mainWindow()) def show_help(self): #QDesktopServices.openUrl(QUrl("file:" + os.path.join(os.path.dirname(__file__), "help/html", "index.html")) QDesktopServices.openUrl(QUrl("https://github.com/rduivenvoorde/kit_dhis2_data_fetcher/")) def initDropdowns(self): # ou = Organisational Units self.ou_model = QStandardItemModel() self.pe_model = QStandardItemModel() self.dx_model = QStandardItemModel() # easy way to add ALL organisationUnits jsono = self.get_json('{}organisationUnits.json?paging=false&level={}'.format(self.api_url, self.level)) if jsono: self.ou_model.appendRow([QStandardItem("ALL"), QStandardItem("ALL")]) for item in jsono['organisationUnits']: display_name = item['displayName'] ou_id = item['id'] #self.info('{} - {}'.format(ou_id, display_name)) self.ou_model.appendRow([QStandardItem(display_name), QStandardItem(ou_id)]) self.dlg.cb_ou.setModel(self.ou_model) else: self.gui_inited = False return False # dx = indicators and data elements # indicators jsono = self.get_json('{}indicators.json?paging=false&level={}'.format(self.api_url, self.level)) if jsono: for item in jsono['indicators']: display_name = item['displayName'] ou_id = item['id'] #self.info('{} - {}'.format(ou_id, display_name)) self.dx_model.appendRow([QStandardItem(display_name), QStandardItem(ou_id)]) else: self.gui_inited = False return False # dataElements jsono = self.get_json('{}dataElements.json?paging=false&level={}'.format(self.api_url, self.level)) if jsono: for item in jsono['dataElements']: display_name = item['displayName'] ou_id = item['id'] #self.info('{} - {}'.format(ou_id, display_name)) self.dx_model.appendRow([QStandardItem(display_name), QStandardItem(ou_id)]) self.dlg.cb_dx.setModel(self.dx_model) else: self.gui_inited = False return False for pe in ['2018', '2017', '2016', '2015', 'LAST_YEAR', 'LAST_5_YEARS', 'THIS_MONTH', 'LAST_MONTH', 'LAST_3_MONTHS', 'MONTHS_THIS_YEAR', 'LAST_12_MONTHS']: self.pe_model.appendRow([QStandardItem(pe), QStandardItem(pe)]) self.dlg.cb_pe.setModel(self.pe_model) self.ou_items = [] self.pe_items = [] self.dx_items = [] self.dlg.cb_ou.setCurrentIndex(-1) self.dlg.cb_dx.setCurrentIndex(-1) self.dlg.cb_pe.setCurrentIndex(-1) self.gui_inited = True self.create_url() self.info('Finish INIT dropdowns') return True def get_json(self, url): jsono = {} try: self.info(url) (response, content) = self.nam.request(url) jsono = json.loads(content.decode('utf-8')) except Exception as e: self.msg(self.tr('Problem retrieving data from: {}'.format(url))) self.info(self.tr('Problem retrieving data from: {}'.format(url))) QMessageBox.warning(self.iface.mainWindow(), self.MSG_TITLE, self.tr('Problem retrieving data from:\n{}' '\nTest this url in browser to check if the api service is available,\nOR' '\nChoose or define another service configuration.'.format(url)), QMessageBox.Ok, QMessageBox.Ok) return False return jsono def load_geodata_in_layer(self): self.info('Loading level {} geodata'.format(self.level)) url = "{}organisationUnits.geojson?paging=false&level={} authcfg='{}'".format(self.api_url, self.level, self.auth_id) geojson_layer = QgsVectorLayer(url, 'Level {} organisationUnits'.format(self.level), 'ogr') if geojson_layer.isValid(): QgsProject.instance().addMapLayer(geojson_layer) else: self.info('Problem loading: {}'.format(url)) def new_dataset(self): #self.info('Clean dataset url') # by setting another level, the url is cleaned self.cb_level_changed(0) def cb_ou_changed(self, index): #self.info('ou index change: {}'.format(index)) if index < 0: return ou_id = self.ou_model.index(index, 1).data() #self.info('ou: {} {} {}'.format(index, ou_id, self.ou_model.index(index, 0).data())) if ou_id == 'ALL': # start with a clean sheet first: self.ou_items = [] for idx in range(0, self.ou_model.rowCount()-1): self.ou_items.append(self.ou_model.index(idx, 1).data()) elif ou_id in self.ou_items: self.ou_items.remove(ou_id) else: self.ou_items.append(ou_id) self.create_url() def cb_pe_changed(self, index): #self.info('pe index change: {}'.format(index)) if index < 0: return pe_id = self.pe_model.index(index, 1).data() #self.info('Selected pe: {}'.format(pe_id)) # id if pe_id in self.pe_items: self.pe_items.remove(pe_id) else: self.pe_items.append(pe_id) self.create_url() def cb_dx_changed(self, index): #self.info('dx index change: {}'.format(index)) if index < 0: return dx_id = self.dx_model.index(index, 1).data() #self.info('Selected dx: {} {} {}'.format(index, dx_id, self.dx_model.index(index, 0).data())) # displayName if dx_id in self.dx_items: self.dx_items.remove(dx_id) else: self.dx_items.append(dx_id) self.create_url() def cb_level_changed(self, index): # redo dropdowns to the Level chossen self.gui_inited = False self.level = self.dlg.cb_level.currentText() #self.info('Level change to {}'.format(self.level)) self.info('initDropdowns in cb_level_changed') if self.initDropdowns(): self.create_url() self.dlg.grp_api.setEnabled(True) else: self.dlg.grp_api.setEnabled(False) def create_url(self): #self.info('Updating analytics url') url = '{}analytics.json?dimension=dx:{}&dimension=pe:{}&dimension=ou:{}&level={}'\ .format(self.api_url, ';'.join(self.dx_items), ';'.join(self.pe_items), ';'.join(self.ou_items), self.level) self.dlg.le_url.setText(url) self.analytics_url = url if len(self.dx_items) == 0 or len(self.pe_items) == 0 or len(self.ou_items) == 0: return False return True def show_dialog(self): """ :return: """ self.dlg.show() # show the dialog # but FIRST check if user recently used a config_id self.dlg.cmb_profile_select.setConfigId(QgsSettings().value('last_conf_id', '')) # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: if self.dlg.cmb_profile_select.configId() is '': self.msg(self.tr('No profile found, please choose or create profile first')) return if self.create_url() is False: self.msg(self.tr('Missing information, select an option from all dropdowns first')) return # ALWAYS grab url from dialog, as it is possible that user copied changed something there self.analytics_url = self.dlg.le_url.text() self.json2features(self.analytics_url) else: # Cancel was clicked pass def update_dhis2_project(self): # now go over layers and check if they have a dhis2_url property p = QgsProject.instance() for lname in p.mapLayers(): lyr = p.mapLayer(lname) url = lyr.customProperty('dhis2_url', '') if len(url) > 0: #self.info('OK url !!') self.info(url) # if so: fetch fresh data, but reuse layer self.json2features(url, lyr) def json2features(self, url, data_layer=None): try: (response, content) = self.nam.request(url, method="GET") except RequestsException as e: self.info('ERROR: {}'.format(e)) return jsons = content.decode('utf-8') jsono = json.loads(jsons) #print(json.dumps(jsono, sort_keys=True, indent=4)) #print(jsono['height']) # creating memory layer with uri: # https://qgis.org/api/qgsmemoryproviderutils_8cpp_source.html if data_layer is None: data_layer = QgsVectorLayer('none', 'DHIS2 data', 'memory') fields = QgsFields() fields.append(QgsField('id', QVariant.String)) fields.append(QgsField('name', QVariant.String)) metadata_items = jsono['metaData']['items'] # create as much fields as there are pe_dx combinations # eg: 2017_birth, 2016_birth, 2017_measels, 2016_measels for pe in jsono['metaData']['dimensions']['pe']: for dx in jsono['metaData']['dimensions']['dx']: #field_alias = '{} {} ({})'.format(pe, metadata_items[dx]['name'], dx) field_alias = '{} {}'.format(pe, metadata_items[dx]['name']) field = QgsField('{}_{}'.format(pe, dx), QVariant.Double, comment=field_alias) field.setAlias(field_alias) fields.append(field) #self.info('Fields: {}'.format(fields)) # clean up first data_layer.dataProvider().deleteAttributes(data_layer.dataProvider().attributeIndexes()) data_layer.updateFields() # set new attributes data_layer.dataProvider().addAttributes(fields) data_layer.updateFields() # array with all features features = [] # map which maps the every feature to its OrganisationalUnit feature_map = {} # create a feature for every ou/OrganisationUnit # AND make sure it has the pe_dx combination fields for ou in jsono['metaData']['dimensions']['ou']: #print(ou) f = QgsFeature() # set fields f.setFields(fields) # set id and name of the feature f.setAttribute('id', ou) f.setAttribute('name', jsono['metaData']['items'][ou]['name']) # append feature to features array and feature map features.append(f) feature_map[ou] = f # dynamic? # currently the order in which they are in the url is below dx_idx = 0 pe_idx = 1 ou_idx = 2 value_idx = 3 # now every cell in the table has a 'row in the json data', with dx, pe, ou and value for row in jsono['rows']: # pick feature based on OrganisationalUnit-key from the feature_map f = feature_map[row[ou_idx]] # attribute key is created from pe_dx string attr = '{}_{}'.format(row[pe_idx], row[dx_idx]) # Births attended by skilled health personnel (estimated pregancies) f.setAttribute(attr, row[value_idx]) data_layer.dataProvider().addFeatures(features) # add it to the project QgsProject.instance().addMapLayer(data_layer) # 'save' the data url of the layer into the project properties # https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/settings.html data_layer.setCustomProperty("dhis2_url", url) def selectAuthConfig(self, conf_id): self.info('"{}" selected as conf_id'.format(conf_id)) QgsSettings().setValue('last_conf_id', conf_id) if conf_id is '': self.dlg.grp_api.setEnabled(False) else: auth_man = QgsApplication.authManager() uri = auth_man.availableAuthMethodConfigs()[conf_id].uri().strip() if uri.startswith('http'): # make sure it ends with / if not uri.endswith('/'): uri = uri + '/' self.api_url = uri self.auth_id = conf_id # Set authid to use to 'dhis2ap' which has api url: https://play.dhis2.org/2.29/api/ self.info("Set authid to use to '{}' which has api url: {}".format(self.auth_id, self.api_url)) # note: user has to create an authenticaton configuration with id 'self.auth_id' to authorize the HTTP requests self.nam = NetworkAccessManager(authid=self.auth_id, exception_class=RequestsException, debug=False) self.info('initDropdowns in selectAuthConfig') if self.initDropdowns(): self.dlg.grp_api.setEnabled(True) else: self.dlg.grp_api.setEnabled(False) else: self.info('Uri should start with "http", now it is: "{}"'.format(uri)) # 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('DHIS2DataFetcher', message) def add_action( self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: # self.iface.addPluginToMenu( # self.menu, # action) self.menu.addAction(action) self.actions.append(action) return action
class Dialog(QDialog, Ui_attr2BUGS): def __init__(self, iface, ml): """Constructor for the dialog. Args: iface: QgsInterface instance. """ QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.setWindowTitle('Attributes to BUGS') self.ml = ml self.ids = [] self.polynum = self.ml.featureCount() self.model = QStandardItemModel(0, 1) self.listView.setModel(self.model) self.model.setHeaderData(0, Qt.Horizontal, 'Field') provider = self.ml.dataProvider() fields = provider.fields() for f in fields: item = QStandardItem(f.name()) item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setData(Qt.Unchecked, Qt.CheckStateRole) self.model.appendRow(item) self.control() self.pushButton.clicked.connect(self.konv) self.pushButton_2.clicked.connect(self.close) self.pushButton_3.clicked.connect(self.save) def konv(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.plainTextEdit.clear() mod = min(self.ids) provider = self.ml.dataProvider() fields = provider.fields() lst = '#data\nlist(' for row in range(0, self.model.rowCount()): it = self.model.itemFromIndex(self.model.index(row, 0)) if it.checkState() == 2: fld = str(self.model.itemData(self.model.index(row, 0))[0]) var = fld + '=c(' ft = fields[row].type() if ft == 10: for ne in range(mod, self.polynum + mod): feat = QgsFeature() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): u = feat.attribute(fld) var += "'%s'," % u var = var[:-2] + "')" else: for ne in range(mod, self.polynum + mod): feat = QgsFeature() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): u = feat.attribute(fld) var += "%s," % u var = var[:-1] + ')' lst += var + ', ' lst += ')' lst = lst.replace('), )', '))') self.plainTextEdit.appendPlainText(lst) QApplication.restoreOverrideCursor() def control(self): feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) #self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, self.polynum)) ne = 0 while feats.nextFeature(feat): ne += 1 #self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) self.ids.append(feat.id()) def save(self): fileName, _ = QFileDialog.getSaveFileName(self, caption='Save As...') try: file = QFile(fileName + '.txt') file.open(QIODevice.WriteOnly | QIODevice.Text) out = QTextStream(file) out << self.plainTextEdit.toPlainText() out.flush() file.close() self.close() return True except IOError: return False
class DialogExportData(QDialog, DIALOG_UI): ValidExtensions = ['xtf', 'itf', 'gml', 'xml'] current_row_schema = 0 def __init__(self, iface, qgis_utils, conn_manager): QDialog.__init__(self) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.iface = iface self.conn_manager = conn_manager self.db = self.conn_manager.get_db_connector_from_source() self.qgis_utils = qgis_utils self.base_configuration = BaseConfiguration() self.ilicache = IliCache(self.base_configuration) self.ilicache.refresh() self._conf_db = ConfigDbSupported() self._params = None self._current_db = None self.xtf_file_browse_button.clicked.connect( make_save_file_selector( self.xtf_file_line_edit, title=QCoreApplication.translate("DialogExportData", "Save in XTF Transfer File"), file_filter=QCoreApplication.translate( "DialogExportData", "XTF Transfer File (*.xtf);;Interlis 1 Transfer File (*.itf);;XML (*.xml);;GML (*.gml)" ), extension='.xtf', extensions=['.' + ext for ext in self.ValidExtensions])) self.xtf_file_browse_button.clicked.connect( self.xtf_browser_opened_to_true) self.xtf_browser_was_opened = False self.validators = Validators() fileValidator = FileValidator( pattern=['*.' + ext for ext in self.ValidExtensions], allow_non_existing=True) self.xtf_file_line_edit.setPlaceholderText( QCoreApplication.translate("DialogExportData", "[Name of the XTF to be created]")) self.xtf_file_line_edit.setValidator(fileValidator) self.xtf_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.xtf_file_line_edit.textChanged.connect( self.xtf_browser_opened_to_false) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) self.connection_setting_button.clicked.connect(self.show_settings) self.connection_setting_button.setText( QCoreApplication.translate("DialogExportData", "Connection Settings")) # LOG self.log_config.setTitle( QCoreApplication.translate("DialogExportData", "Show log")) self.log_config.setFlat(True) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self._accept_button = self.buttonBox.addButton( QCoreApplication.translate("DialogExportData", "Export data"), QDialogButtonBox.AcceptRole) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.show_help) self.update_connection_info() self.update_model_names() self.restore_configuration() def update_connection_info(self): db_description = self.db.get_description_conn_string() if db_description: self.db_connect_label.setText(db_description) self.db_connect_label.setToolTip(self.db.get_display_conn_string()) self._accept_button.setEnabled(True) else: self.db_connect_label.setText( QCoreApplication.translate("DialogExportData", "The database is not defined!")) self.db_connect_label.setToolTip('') self._accept_button.setEnabled(False) def update_model_names(self): self.export_models_qmodel = QStandardItemModel() db_models = None db_models = self.db.get_models() if db_models: for db_model in db_models: regex = re.compile(r'(?:\{[^\}]*\}|\s)') for modelname in regex.split(db_model['modelname']): if modelname and modelname not in DEFAULT_HIDDEN_MODELS: item = QStandardItem(modelname.strip()) item.setCheckable(False) item.setEditable(False) self.export_models_qmodel.appendRow(item) self.export_models_list_view.setModel(self.export_models_qmodel) def get_ili_models(self): ili_models = list() for index in range(self.export_models_qmodel.rowCount()): item = self.export_models_qmodel.item(index) ili_models.append(item.text()) return ili_models def show_settings(self): dlg = SettingsDialog(qgis_utils=self.qgis_utils, conn_manager=self.conn_manager) # Connect signals (DBUtils, QgisUtils) dlg.db_connection_changed.connect( self.conn_manager.db_connection_changed) dlg.db_connection_changed.connect( self.qgis_utils.cache_layers_and_relations) dlg.organization_tools_changed.connect( self.qgis_utils.organization_tools_changed) dlg.set_action_type(EnumDbActionType.EXPORT) dlg.tabWidget.setCurrentIndex(SETTINGS_CONNECTION_TAB_INDEX) if dlg.exec_(): self.db = dlg.get_db_connection() self.update_model_names() self.update_connection_info() def accepted(self): configuration = self.update_configuration() if not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable: message_error = QCoreApplication.translate( "DialogExportData", "Please set a valid XTF file before exporting data.") self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.xtf_file_line_edit.setFocus() return if not self.get_ili_models(): message_error = QCoreApplication.translate( "DialogExportData", "Please set a valid schema to export. This schema does not have information to export." ) self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.export_models_list_view.setFocus() return if not configuration.iliexportmodels: message_error = QCoreApplication.translate( "DialogExportData", "Please set a model before exporting data.") self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.export_models_list_view.setFocus() return # If xtf browser was opened and the file exists, the user already chose # to overwrite the file if os.path.isfile(self.xtf_file_line_edit.text().strip() ) and not self.xtf_browser_was_opened: self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.msg.setText( QCoreApplication.translate( "DialogExportData", "{filename} already exists.\nDo you want to replace it?"). format(filename=os.path.basename( self.xtf_file_line_edit.text().strip()))) self.msg.setWindowTitle( QCoreApplication.translate("DialogExportData", "Save in XTF Transfer File")) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box = self.msg.exec_() if msg_box == QMessageBox.No: return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.progress_bar.setValue(0) self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() exporter = iliexporter.Exporter() item_db = self._conf_db.get_db_items()[self.db.mode] exporter.tool = item_db.get_mbaker_db_ili_mode() exporter.configuration = configuration self.save_configuration(configuration) exporter.stdout.connect(self.print_info) exporter.stderr.connect(self.on_stderr) exporter.process_started.connect(self.on_process_started) exporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if exporter.run() != iliexporter.Exporter.SUCCESS: self.enable() self.progress_bar.hide() self.show_message( QCoreApplication.translate( "DialogExportData", "An error occurred when exporting the data. For more information see the log..." ), Qgis.Warning) return except JavaNotFoundError: # Set JAVA PATH get_java_path_dlg = GetJavaPathDialog() get_java_path_dlg.setModal(True) if get_java_path_dlg.exec_(): configuration = self.update_configuration() if not get_java_path_from_qgis_model_baker(): message_error_java = QCoreApplication.translate( "DialogExportData", """Java could not be found. You can configure the JAVA_HOME environment variable, restart QGIS and try again.""" ) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(message_error_java) self.show_message(message_error_java, Qgis.Warning) self.enable() self.progress_bar.hide() return self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) self.show_message( QCoreApplication.translate( "DialogExportData", "Export of the data was successfully completed."), Qgis.Success) def save_configuration(self, configuration): settings = QSettings() settings.setValue( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_export', configuration.xtffile) settings.setValue('Asistente-LADM_COL/QgisModelBaker/show_log', not self.log_config.isCollapsed()) def restore_configuration(self): settings = QSettings() self.xtf_file_line_edit.setText( settings.value( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_export')) # Show log value_show_log = settings.value( 'Asistente-LADM_COL/QgisModelBaker/show_log', False, type=bool) self.log_config.setCollapsed(not value_show_log) # set model repository # if there is no option by default use online model repository custom_model_is_checked = settings.value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', type=bool) if custom_model_is_checked: self.custom_model_directories = settings.value( 'Asistente-LADM_COL/models/custom_models') def update_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ item_db = self._conf_db.get_db_items()[self.db.mode] configuration = ExportConfiguration() item_db.set_db_configuration_params(self.db.dict_conn_params, configuration) configuration.xtffile = self.xtf_file_line_edit.text().strip() java_path = get_java_path_from_qgis_model_baker() if java_path: self.base_configuration.java_path = java_path # Check custom model directories if QSettings().value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', type=bool): if self.custom_model_directories is None: self.base_configuration.custom_model_directories_enabled = False else: self.base_configuration.custom_model_directories = self.custom_model_directories self.base_configuration.custom_model_directories_enabled = True configuration.base_configuration = self.base_configuration if self.get_ili_models(): configuration.iliexportmodels = ';'.join(self.get_ili_models()) configuration.ilimodels = ';'.join(self.get_ili_models()) configuration.disable_validation = not QSettings().value( 'Asistente-LADM_COL/advanced_settings/validate_data_importing_exporting', True, bool) return configuration def print_info(self, text, text_color='#000000', clear=False): self.txtStdout.setTextColor(QColor(text_color)) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = '#004905' if exit_code == 0 else '#aa2222' self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append( QCoreApplication.translate("DialogExportData", "Finished ({})").format(exit_code)) if result == iliexporter.Exporter.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: self.enable() # Open log if self.log_config.isCollapsed(): self.log_config.setCollapsed(False) def advance_progress_bar_by_text(self, text): if text.strip() == 'Info: compile models...': self.progress_bar.setValue(50) QCoreApplication.processEvents() elif text.strip() == 'Info: process data...': self.progress_bar.setValue(55) QCoreApplication.processEvents() elif text.strip() == 'Info: first validation pass...': self.progress_bar.setValue(70) QCoreApplication.processEvents() elif text.strip() == 'Info: second validation pass...': self.progress_bar.setValue(85) QCoreApplication.processEvents() def show_help(self): self.qgis_utils.show_help("export_data") def disable(self): self.db_config.setEnabled(False) self.ili_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.db_config.setEnabled(True) self.ili_config.setEnabled(True) self.buttonBox.setEnabled(True) def show_message(self, message, level): self.bar.pushMessage("Asistente LADM_COL", message, level, duration=0) def xtf_browser_opened_to_true(self): """ Slot. Sets a flag to true to eventually avoid asking a user whether to overwrite a file. """ self.xtf_browser_was_opened = True def xtf_browser_opened_to_false(self): """ Slot. Sets a flag to false to eventually ask a user whether to overwrite a file. """ self.xtf_browser_was_opened = False
class DialogImportData(QDialog, DIALOG_UI): open_dlg_import_schema = pyqtSignal(dict) # dict with key-value params BUTTON_NAME_IMPORT_DATA = QCoreApplication.translate( "DialogImportData", "Import data") BUTTON_NAME_GO_TO_CREATE_STRUCTURE = QCoreApplication.translate( "DialogImportData", "Go to Create Structure...") def __init__(self, iface, conn_manager, context, link_to_import_schema=True, parent=None): QDialog.__init__(self, parent) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.iface = iface self.conn_manager = conn_manager self.db_source = context.get_db_sources()[0] self.link_to_import_schema = link_to_import_schema self.db = self.conn_manager.get_db_connector_from_source( self.db_source) self.base_configuration = BaseConfiguration() self.logger = Logger() self.app = AppInterface() self.__ladmcol_models = LADMColModelRegistry() self.java_dependency = JavaDependency() self.java_dependency.download_dependency_completed.connect( self.download_java_complete) self.java_dependency.download_dependency_progress_changed.connect( self.download_java_progress_change) self.ilicache = IliCache(self.base_configuration) self.ilicache.refresh() self._dbs_supported = ConfigDBsSupported() self._running_tool = False # There may be 1 case where we need to emit a db_connection_changed from the Import Data dialog: # 1) Connection Settings was opened and the DB conn was changed. self._db_was_changed = False # To postpone calling refresh gui until we close this dialog instead of settings # Similarly, we could call a refresh on layers and relations cache in 1 case: # 1) If the ID dialog was called for the COLLECTED source: opening Connection Settings and changing the DB # connection. self._schedule_layers_and_relations_refresh = False # We need bar definition above calling clear_messages self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) self.xtf_file_browse_button.clicked.connect( make_file_selector( self.xtf_file_line_edit, title=QCoreApplication.translate( "DialogImportData", "Open Transfer or Catalog File"), file_filter=QCoreApplication.translate( "DialogImportData", 'Transfer File (*.xtf *.itf);;Catalogue File (*.xml *.xls *.xlsx)' ))) self.validators = Validators() self.xtf_file_line_edit.setPlaceholderText( QCoreApplication.translate("DialogImportData", "[Name of the XTF to be imported]")) fileValidator = FileValidator(pattern=['*.xtf', '*.itf', '*.xml']) self.xtf_file_line_edit.setValidator(fileValidator) self.xtf_file_line_edit.textChanged.connect(self.update_import_models) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) # db self.connection_setting_button.clicked.connect(self.show_settings) self.connection_setting_button.setText( QCoreApplication.translate("DialogImportData", "Connection Settings")) # LOG self.log_config.setTitle( QCoreApplication.translate("DialogImportData", "Show log")) self.buttonBox.accepted.disconnect() self.buttonBox.clicked.connect(self.accepted_import_data) self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self._accept_button = self.buttonBox.addButton( self.BUTTON_NAME_IMPORT_DATA, QDialogButtonBox.AcceptRole) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.show_help) self.update_connection_info() self.restore_configuration() def accepted_import_data(self, button): if self.buttonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: if button.text() == self.BUTTON_NAME_IMPORT_DATA: self.accepted() elif button.text() == self.BUTTON_NAME_GO_TO_CREATE_STRUCTURE: self.close() # Close import data dialog self.open_dlg_import_schema.emit({ 'selected_models': self.get_ili_models() }) # Emit signal to open import schema dialog def reject(self): if self._running_tool: QMessageBox.information( self, QCoreApplication.translate("DialogImportData", "Warning"), QCoreApplication.translate( "DialogImportData", "The Import Data tool is still running. Please wait until it finishes." )) else: self.close_dialog() def close_dialog(self): """ We use this method to be safe when emitting the db_connection_changed, otherwise we could trigger slots that unload the plugin, destroying dialogs and thus, leading to crashes. """ if self._schedule_layers_and_relations_refresh: self.conn_manager.db_connection_changed.connect( self.app.core.cache_layers_and_relations) if self._db_was_changed: # If the db was changed, it implies it complies with ladm_col, hence the second parameter self.conn_manager.db_connection_changed.emit( self.db, True, self.db_source) if self._schedule_layers_and_relations_refresh: self.conn_manager.db_connection_changed.disconnect( self.app.core.cache_layers_and_relations) self.logger.info(__name__, "Dialog closed.") self.done(QDialog.Accepted) def update_connection_info(self): db_description = self.db.get_description_conn_string() if db_description: self.db_connect_label.setText(db_description) self.db_connect_label.setToolTip(self.db.get_display_conn_string()) self._accept_button.setEnabled(True) else: self.db_connect_label.setText( QCoreApplication.translate("DialogImportData", "The database is not defined!")) self.db_connect_label.setToolTip('') self._accept_button.setEnabled(False) def update_import_models(self): self.clear_messages() error_msg = None if not self.xtf_file_line_edit.text().strip(): color = '#ffd356' # Light orange self.import_models_qmodel = QStandardItemModel() self.import_models_list_view.setModel(self.import_models_qmodel) else: if os.path.isfile(self.xtf_file_line_edit.text().strip()): color = '#fff' # White self.import_models_qmodel = QStandardItemModel() model_names = get_models_from_xtf( self.xtf_file_line_edit.text().strip()) for model in self.__ladmcol_models.supported_models(): if not model.hidden() and model.full_name() in model_names: item = QStandardItem(model.full_alias()) item.setData(model.full_name(), Qt.UserRole) item.setCheckable(False) item.setEditable(False) self.import_models_qmodel.appendRow(item) if self.import_models_qmodel.rowCount() > 0: self.import_models_list_view.setModel( self.import_models_qmodel) else: error_msg = QCoreApplication.translate( "DialogImportData", "No models were found in the XTF. Is it a valid file?") color = '#ffd356' # Light orange self.import_models_qmodel = QStandardItemModel() self.import_models_list_view.setModel( self.import_models_qmodel) else: error_msg = QCoreApplication.translate( "DialogImportData", "Please set a valid XTF file") color = '#ffd356' # Light orange self.import_models_qmodel = QStandardItemModel() self.import_models_list_view.setModel( self.import_models_qmodel) self.xtf_file_line_edit.setStyleSheet( 'QLineEdit {{ background-color: {} }}'.format(color)) if error_msg: self.txtStdout.setText(error_msg) self.show_message(error_msg, Qgis.Warning) self.import_models_list_view.setFocus() return def get_ili_models(self): ili_models = list() for index in range(self.import_models_qmodel.rowCount()): item = self.import_models_qmodel.item(index) ili_models.append(item.data(Qt.UserRole)) return ili_models def show_settings(self): # We only need those tabs related to Model Baker/ili2db operations dlg = SettingsDialog(self.conn_manager, parent=self) dlg.setWindowTitle( QCoreApplication.translate("DialogImportData", "Target DB Connection Settings")) dlg.show_tip( QCoreApplication.translate( "DialogImportData", "Configure where do you want the XTF data to be imported.")) dlg.set_db_source(self.db_source) dlg.set_tab_pages_list( [SETTINGS_CONNECTION_TAB_INDEX, SETTINGS_MODELS_TAB_INDEX]) # Connect signals (DBUtils, Core) dlg.db_connection_changed.connect(self.db_connection_changed) if self.db_source == COLLECTED_DB_SOURCE: self._schedule_layers_and_relations_refresh = True dlg.set_action_type(EnumDbActionType.IMPORT) if dlg.exec_(): self.db = dlg.get_db_connection() self.update_connection_info() def db_connection_changed(self, db, ladm_col_db, db_source): self._db_was_changed = True self.clear_messages() def accepted(self): self._running_tool = True self.txtStdout.clear() self.progress_bar.setValue(0) self.bar.clearWidgets() if not os.path.isfile(self.xtf_file_line_edit.text().strip()): self._running_tool = False error_msg = QCoreApplication.translate( "DialogImportData", "Please set a valid XTF file before importing data. XTF file does not exist." ) self.txtStdout.setText(error_msg) self.show_message(error_msg, Qgis.Warning) self.xtf_file_line_edit.setFocus() return java_home_set = self.java_dependency.set_java_home() if not java_home_set: message_java = QCoreApplication.translate( "DialogImportData", """Configuring Java {}...""").format(JAVA_REQUIRED_VERSION) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(message_java) self.java_dependency.get_java_on_demand() self.disable() return configuration = self.update_configuration() if configuration.disable_validation: # If data validation at import is disabled, we ask for confirmation self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Question) self.msg.setText( QCoreApplication.translate( "DialogImportData", "Are you sure you want to import your data without validation?" )) self.msg.setWindowTitle( QCoreApplication.translate("DialogImportData", "Import XTF without validation?")) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) res = self.msg.exec_() if res == QMessageBox.No: self._running_tool = False return if not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable: self._running_tool = False error_msg = QCoreApplication.translate( "DialogImportData", "Please set a valid XTF before importing data.") self.txtStdout.setText(error_msg) self.show_message(error_msg, Qgis.Warning) self.xtf_file_line_edit.setFocus() return if not self.get_ili_models(): self._running_tool = False error_msg = QCoreApplication.translate( "DialogImportData", "The selected XTF file does not have information according to the LADM-COL model to import." ) self.txtStdout.setText(error_msg) self.show_message(error_msg, Qgis.Warning) self.import_models_list_view.setFocus() return # Get list of models present in the XTF file, in the DB and in the list of required models (by the plugin) ili_models = set([ili_model for ili_model in self.get_ili_models()]) supported_models_in_ili = set([ m.full_name() for m in self.__ladmcol_models.supported_models() ]).intersection(ili_models) if not supported_models_in_ili: self._running_tool = False error_msg = QCoreApplication.translate("DialogImportData", "The selected XTF file does not have data from any LADM-COL model supported by the LADM-COL Assistant. " \ "Therefore, you cannot import it! These are the models supported:\n\n * {}").format(" \n * ".join([m.full_alias() for m in self.__ladmcol_models.supported_models()])) self.txtStdout.setText(error_msg) self.show_message(error_msg, Qgis.Warning) self.import_models_list_view.setFocus() return db_models = set(self.db.get_models()) suggested_models = sorted(ili_models.difference(db_models)) if not ili_models.issubset(db_models): self._running_tool = False error_msg = QCoreApplication.translate("DialogImportData", "IMPORT ERROR: The XTF file to import does not have the same models as the target database schema. " \ "Please create a schema that also includes the following missing modules:\n\n * {}").format( " \n * ".join(suggested_models)) self.txtStdout.clear() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.setText(error_msg) self.show_message(error_msg, Qgis.Warning) self.xtf_file_line_edit.setFocus() # button is removed to define order in GUI for button in self.buttonBox.buttons(): if button.text() == self.BUTTON_NAME_IMPORT_DATA: self.buttonBox.removeButton(button) # Check if button was previously added self.remove_create_structure_button() if self.link_to_import_schema: self.buttonBox.addButton( self.BUTTON_NAME_GO_TO_CREATE_STRUCTURE, QDialogButtonBox.AcceptRole).setStyleSheet( "color: #aa2222;") self.buttonBox.addButton(self.BUTTON_NAME_IMPORT_DATA, QDialogButtonBox.AcceptRole) return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() dataImporter = iliimporter.Importer(dataImport=True) db_factory = self._dbs_supported.get_db_factory(self.db.engine) dataImporter.tool = db_factory.get_model_baker_db_ili_mode() dataImporter.configuration = configuration self.save_configuration(configuration) dataImporter.stdout.connect(self.print_info) dataImporter.stderr.connect(self.on_stderr) dataImporter.process_started.connect(self.on_process_started) dataImporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if dataImporter.run() != iliimporter.Importer.SUCCESS: self._running_tool = False self.show_message( QCoreApplication.translate( "DialogImportData", "An error occurred when importing the data. For more information see the log..." ), Qgis.Warning) return except JavaNotFoundError: self._running_tool = False error_msg_java = QCoreApplication.translate( "DialogImportData", "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again." ).format(JAVA_REQUIRED_VERSION) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(error_msg_java) self.show_message(error_msg_java, Qgis.Warning) return self._running_tool = False self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) self.show_message( QCoreApplication.translate( "DialogImportData", "Import of the data was successfully completed"), Qgis.Success) def download_java_complete(self): self.accepted() def download_java_progress_change(self, progress): self.progress_bar.setValue(progress / 2) if (progress % 20) == 0: self.txtStdout.append('...') def remove_create_structure_button(self): for button in self.buttonBox.buttons(): if button.text() == self.BUTTON_NAME_GO_TO_CREATE_STRUCTURE: self.buttonBox.removeButton(button) def save_configuration(self, configuration): settings = QSettings() settings.setValue( 'Asistente-LADM-COL/QgisModelBaker/ili2pg/xtffile_import', configuration.xtffile) settings.setValue('Asistente-LADM-COL/QgisModelBaker/show_log', not self.log_config.isCollapsed()) def restore_configuration(self): settings = QSettings() self.xtf_file_line_edit.setText( settings.value( 'Asistente-LADM-COL/QgisModelBaker/ili2pg/xtffile_import')) # Show log value_show_log = settings.value( 'Asistente-LADM-COL/QgisModelBaker/show_log', False, type=bool) self.log_config.setCollapsed(not value_show_log) # set model repository # if there is no option by default use online model repository self.use_local_models = settings.value( 'Asistente-LADM-COL/models/custom_model_directories_is_checked', DEFAULT_USE_CUSTOM_MODELS, type=bool) if self.use_local_models: self.custom_model_directories = settings.value( 'Asistente-LADM-COL/models/custom_models', DEFAULT_MODELS_DIR) def update_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ db_factory = self._dbs_supported.get_db_factory(self.db.engine) configuration = ImportDataConfiguration() db_factory.set_ili2db_configuration_params(self.db.dict_conn_params, configuration) configuration.xtffile = self.xtf_file_line_edit.text().strip() configuration.delete_data = False configuration.srs_auth = QSettings().value( 'Asistente-LADM-COL/QgisModelBaker/srs_auth', DEFAULT_SRS_AUTH, str) configuration.srs_code = QSettings().value( 'Asistente-LADM-COL/QgisModelBaker/srs_code', int(DEFAULT_SRS_CODE), int) configuration.inheritance = ILI2DBNames.DEFAULT_INHERITANCE configuration.create_basket_col = ILI2DBNames.CREATE_BASKET_COL configuration.create_import_tid = ILI2DBNames.CREATE_IMPORT_TID configuration.stroke_arcs = ILI2DBNames.STROKE_ARCS configuration.with_importtid = True full_java_exe_path = JavaDependency.get_full_java_exe_path() if full_java_exe_path: self.base_configuration.java_path = full_java_exe_path # User could have changed the default values self.use_local_models = QSettings().value( 'Asistente-LADM-COL/models/custom_model_directories_is_checked', DEFAULT_USE_CUSTOM_MODELS, type=bool) self.custom_model_directories = QSettings().value( 'Asistente-LADM-COL/models/custom_models', DEFAULT_MODELS_DIR) # Check custom model directories if self.use_local_models: if not self.custom_model_directories: self.base_configuration.custom_model_directories_enabled = False else: self.base_configuration.custom_model_directories = self.custom_model_directories self.base_configuration.custom_model_directories_enabled = True configuration.base_configuration = self.base_configuration if self.get_ili_models(): configuration.ilimodels = ';'.join(self.get_ili_models()) configuration.disable_validation = not QSettings().value( 'Asistente-LADM-COL/models/validate_data_importing_exporting', True, bool) return configuration def print_info(self, text, text_color='#000000', clear=False): self.txtStdout.setTextColor(QColor(text_color)) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = '#004905' if exit_code == 0 else '#aa2222' self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append('Finished ({})'.format(exit_code)) if result == iliimporter.Importer.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: self.show_message( QCoreApplication.translate("DialogImportData", "Error when importing data"), Qgis.Warning) # Open log if self.log_config.isCollapsed(): self.log_config.setCollapsed(False) def advance_progress_bar_by_text(self, text): if text.strip() == 'Info: compile models...': self.progress_bar.setValue(50) QCoreApplication.processEvents() elif text.strip() == 'Info: create table structure...': self.progress_bar.setValue(55) QCoreApplication.processEvents() elif text.strip() == 'Info: first validation pass...': self.progress_bar.setValue(60) QCoreApplication.processEvents() elif text.strip() == 'Info: second validation pass...': self.progress_bar.setValue(80) QCoreApplication.processEvents() def clear_messages(self): self.bar.clearWidgets( ) # Remove previous messages before showing a new one self.txtStdout.clear() # Clear previous log messages self.progress_bar.setValue(0) # Initialize progress bar def show_help(self): show_plugin_help("import_data") def disable(self): self.source_config.setEnabled(False) self.target_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.source_config.setEnabled(True) self.target_config.setEnabled(True) self.buttonBox.setEnabled(True) def show_message(self, message, level): if level == Qgis.Warning: self.enable() self.bar.clearWidgets( ) # Remove previous messages before showing a new one self.bar.pushMessage("Asistente LADM-COL", message, level, duration=0)
class RuimtelijkePlannen(object): """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 and the mapCanvas self.iface = iface self.mapcanvas = iface.mapCanvas() # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale if not QSettings().value('locale/userLocale'): locale = QSettings().value('locale/globalLocale')[0:2] else: locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join(self.plugin_dir, 'i18n', 'RuimtelijkePlannen_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Ruimtelijke Plannen') self.toolbar = self.iface.addToolBar(u'RuimtelijkePlannen') self.toolbar.setObjectName(u'RuimtelijkePlannen') self.nam = networkaccessmanager.NetworkAccessManager() self.project = QgsProject.instance() # initialize styles folder self.settings = QSettings() self.available_styles_folder = os.path.join(self.plugin_dir, 'styles') self.map_style = self.settings.value("RuimtelijkePlannen/map_style", "print") self.map_style_folder = os.path.join(self.available_styles_folder, self.map_style) if not os.path.isdir(self.map_style_folder): self.map_style = "print" self.map_style_folder = os.path.join(self.available_styles_folder, self.map_style) 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 """ return QCoreApplication.translate('RuimtelijkePlannen', message) def add_action(self, dialog, 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 """ if dialog: self.dlg = dialog icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToWebMenu(self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" self.dlg = RuimtelijkePlannenDialog() self.settings_dlg = RuimtelijkePlannenSettingsDialog() self.add_action(dialog=None, icon_path=':/plugins/RuimtelijkePlannen/help.png', text=self.tr(u'Help'), callback=self.open_help, parent=self.iface.mainWindow(), add_to_toolbar=False) self.add_action(dialog=None, icon_path=':/plugins/RuimtelijkePlannen/settings.png', text=self.tr(u'Settings'), callback=self.run_settings, parent=self.iface.mainWindow(), add_to_toolbar=False) # add a button to click in map and find bestemmingsplannen self.action = self.add_action( dialog=None, icon_path=':/plugins/RuimtelijkePlannen/icon.png', text=self.tr(u'Click map query RuimtelijkePlannen'), callback=self.activate_tool, add_to_menu=True, status_tip=self.tr(u'Click map query RuimtelijkePlannen'), parent=self.iface.mainWindow()) self.action.setCheckable(True) # add it to the same group as the pan/zoom tools self.iface.actionPan().actionGroup().addAction(self.action) self.xytool = GetPointTool(self.mapcanvas, self.getRPplannenByPoint) self.toolbarSearch = QLineEdit() self.toolbarSearch.setMaximumWidth(200) self.toolbarSearch.setAlignment(QtCore.Qt.AlignLeft) self.toolbarSearch.setPlaceholderText("NL.IMRO.") self.toolbar.addWidget(self.toolbarSearch) self.toolbarSearch.returnPressed.connect(self.addRPplanFromToolbar) self.proxyModel = QSortFilterProxyModel() self.sourceModel = QStandardItemModel() self.proxyModel.setSourceModel(self.sourceModel) self.dlg.treeView_results.setModel(self.proxyModel) self.dlg.treeView_results.setEditTriggers( QAbstractItemView.NoEditTriggers) self.dlg.treeView_results.setSortingEnabled(True) self.dlg.treeView_results.doubleClicked.connect(self.loadRPplan) # have an info icon at hand for keuzehulp self.infoIcon = QIcon(':/plugins/RuimtelijkePlannen/info.png') # have the right crs at hand self.rp_crs = QgsCoordinateReferenceSystem(28992) ## urls to RuimtelijkePlannen ## this one is the traditional one # self.rp_search_url = "https://www.ruimtelijkeplannen.nl/web-roo/rest/search" # self.rp_search_id_resource = "/plan/id/" # self.rp_search_xy_resource = "/plannen/xy/" ## this one is new and has more metadata with the "keuzehulp"! self.rp_search_url = "https://www.ruimtelijkeplannen.nl/plannenservice" self.rp_search_id_resource = "/plannen/identificatie/" self.rp_search_xy_resource = "/plannen/xy/" self.rp_wfs_url = "https://afnemers.ruimtelijkeplannen.nl/afnemers2012/services" # the following lists of plantypes comes from: # https://www.ruimtelijkeplannen.nl/viewer/planoverzicht self.rp_supported_planTypes = [\ 'aanwijzingsbesluit', 'beheersverordening', 'bestemmingsplan', 'exploitatieplan', 'gemeentelijk plan; bestemmingsplan artikel 10', 'gemeentelijk plan; uitwerkingsplan artikel 11', 'gemeentelijk plan; voorbereidingsbesluit', 'gemeentelijk plan; wijzigingsplan artikel 11', 'gemeentelijke visie; overig', 'gerechtelijke uitspraak', 'inpassingsplan', 'omgevingsvergunning', 'projectbesluit', 'reactieve aanwijzing', 'tijdelijke ontheffing buitenplans', 'uitwerkingsplan', 'voorbereidingsbesluit', 'wijzigingsplan' ] self.rp_not_supported_plantypes = [\ # these are the plan types which can have multiple maps 'amvb', 'provinciale verordening', 'regeling', 'structuurvisie' ] # layer action on RuimtelijkePlannen layers to show links to text self.rp_layer_action = QgsAction(QgsAction.GenericPython, 'Open text link(s) in browser ', layer_action, "", False, "", {'Feature', 'Canvas'}) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginWebMenu(self.tr(u'&Ruimtelijke Plannen'), action) self.iface.removeToolBarIcon(action) del self.toolbar def open_help(self): ''' Method to open the help pages ''' QDesktopServices().openUrl( QUrl.fromLocalFile( os.path.join("file://", self.plugin_dir, 'help/build/html', 'index.html'))) def run_settings(self): ''' method showing the settings dialog and act on the results ''' # we do this on opening this setting, so the user can easily add # new styles wthout reloading the plugin. available_styles = next(os.walk(self.available_styles_folder))[1] self.settings_dlg.styleComboBox.clear() self.settings_dlg.styleComboBox.addItems(available_styles) index = self.settings_dlg.styleComboBox.findText(self.map_style) self.settings_dlg.styleComboBox.setCurrentIndex(index) self.settings_dlg.show() result = self.settings_dlg.exec_() if result: map_style_folder = os.path.join( self.available_styles_folder, self.settings_dlg.styleComboBox.currentText()) if os.path.isdir(map_style_folder): self.map_style = self.settings_dlg.styleComboBox.currentText() self.map_style_folder = os.path.join( self.available_styles_folder, self.map_style) self.settings.setValue("RuimtelijkePlannen/map_style", self.map_style) else: self.iface.messageBar().pushMessage( 'Warning', self. tr(u"Selected style folder not found. See message log for details." ), level=Qgis.Warning) QgsMessageLog.logMessage(u'Style folder not found: ' + \ os.path.join(self.available_styles_folder, self.map_style), 'RuimtelijkePlannen') def activate_tool(self): self.mapcanvas.setMapTool(self.xytool) def addRPplanFromToolbar(self): '''slot for toolbarSearch''' self.addFromIdn(self.toolbarSearch.text()) def addFromIdn(self, search_string): '''adds plan from idn''' self.toolbarSearch.clear() url = self.rp_search_url + self.rp_search_id_resource + search_string QgsMessageLog.logMessage(u'Query: ' + str(url), 'RuimtelijkePlannen') QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) (response, content) = self.nam.request(url) self.rp = json.loads(content.decode("utf-8")) QApplication.restoreOverrideCursor() if "ErrorType" in self.rp \ or ('aantal' in self.rp and self.rp['aantal'] == 0): self.iface.messageBar().pushMessage("Error", self.tr(u'Plan not found.'), level=Qgis.Critical) return if 'resultaat' in self.rp: # we are using the keuzeHulp self.rp = self.rp['plannen'][0] if not self.rp["typePlan"] in self.rp_supported_planTypes: self.iface.messageBar().pushMessage( "Error", self.tr(u'Plan type not supported.'), level=Qgis.Critical) else: self.addRPplan_WFS(plangebied=search_string, plantype=self.rp["typePlan"]) def getWfsLayer(self, service, typename, wfs_filter): params = { 'service': 'WFS', 'version': '1.1.0', 'request': 'GetFeature', 'typename': typename, 'filter': wfs_filter, 'srsname': 'EPSG:28992', } if not service[-1] == '?': service += '?' uri = service + urllib.parse.unquote(urllib.parse.urlencode(params)) layername = typename vlayer = QgsVectorLayer(uri, layername, "WFS") return vlayer def addRPplan_WFS(self, plangebied, plantype): '''adds a plan as a WFS layer''' QgsMessageLog.logMessage(u'Adding plan with IDN "%s" and plantype "%s"'\ % (plangebied,plantype), 'RuimtelijkePlannen') # add a layergroup to insert the layers in bpGroup = QgsProject.instance().layerTreeRoot().insertGroup( 0, plangebied) service = self.rp_wfs_url wfs_filter = '"plangebied"=\'' + plangebied + '\'' plan = rp_plan(plantype) for layer in plan.layers: vlayer = self.getWfsLayer(service, layer['name'], wfs_filter) if vlayer.isValid(): if 'qml' in layer: layerQml = os.path.join(self.map_style_folder, layer['qml']) if os.path.exists(layerQml): vlayer.loadNamedStyle(layerQml) else: self.iface.messageBar().pushMessage( 'Warning', self. tr(u"Style not found. See message log for details." ), level=Qgis.Warning) QgsMessageLog.logMessage(u'Style file not found: ' + \ str(layerQml), 'RuimtelijkePlannen') if layer['text_action']: vlayer.actions().addAction(self.rp_layer_action) self.project.addMapLayer(vlayer, False) bpGroup.insertChildNode(-1, QgsLayerTreeLayer(vlayer)) else: self.iface.messageBar().pushMessage( 'Warning', self.tr(u"Invalid layer: ") + layer['name'], level=Qgis.Warning) vlayer.selectAll() canvas = self.iface.mapCanvas() canvas.zoomToSelected(vlayer) vlayer.removeSelection() def loadRPplan(self, index): '''slot for row in search results widget''' self.dlg.hide() self.addRPplan_WFS(plangebied=index.sibling(index.row(), 1).data(), plantype=index.sibling(index.row(), 3).data()) def addSourceRow(self, nr, plan, keuze_hulp=None): '''adds plan to search results widget''' if plan["typePlan"] in self.rp_supported_planTypes: nr = QStandardItem(str(nr + 1)) planId = QStandardItem(plan["identificatie"]) if keuze_hulp: planStatus = QStandardItem(self.infoIcon, plan["planStatus"]) f = planStatus.font() f.setUnderline(True) planStatus.setFont(f) planStatus.setToolTip( '%s: %s' % (keuze_hulp["positieTekst"], keuze_hulp["keuzeHulpTekst"])) else: planStatus = QStandardItem(plan["planStatus"]) planNaam = QStandardItem(plan["naam"]) planType = QStandardItem(plan["typePlan"]) #planGebiedType = QStandardItem(plan["planGebiedType"]) # not yet(?) in use by this plugin self.sourceModel.appendRow( [nr, planId, planStatus, planType, planNaam]) def addSourceRowFromKeuzeHulp(self, nr, plan_en_keuze): '''adds plan to search results widget''' keuze_hulp = plan_en_keuze['keuzeHulp'] plan = plan_en_keuze['planDto'] self.addSourceRow(nr, plan, keuze_hulp) def getRPplannenByPoint(self, event): '''Queries ruimtelijkeplannen by point and shows results on widget.''' xform = QgsCoordinateTransform( self.mapcanvas.mapSettings().destinationCrs(), self.rp_crs, QgsProject.instance()) xy = xform.transform( QgsPointXY( self.mapcanvas.getCoordinateTransform().toMapCoordinates( event.pos().x(), event.pos().y()))) self.sourceModel.clear() url = self.rp_search_url + self.rp_search_xy_resource + str( xy.x()) + "/" + str(xy.y()) QgsMessageLog.logMessage(u'Query: ' + str(url), 'RuimtelijkePlannen') QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) (response, content) = self.nam.request(url) self.rp = json.loads(content.decode("utf-8")) if "ErrorType" in self.rp: self.iface.messageBar().pushMessage("Error", self.tr(u'No Plans found.') + self.rp["ErrorDescription"], level=Qgis.Critical) QApplication.restoreOverrideCursor() return if 'plannen' in self.rp: for nr, plan in enumerate(self.rp["plannen"]): self.addSourceRow(nr, plan) else: for nr, plan_en_keuze in enumerate(self.rp["keuzeHulpPlannen"]): self.addSourceRowFromKeuzeHulp(nr, plan_en_keuze) if not self.sourceModel.rowCount(): QApplication.restoreOverrideCursor() return self.sourceModel.setHeaderData(0, QtCore.Qt.Horizontal, "Nr") self.sourceModel.setHeaderData(1, QtCore.Qt.Horizontal, "Identification") self.sourceModel.horizontalHeaderItem(1).setTextAlignment( QtCore.Qt.AlignLeft) self.sourceModel.setHeaderData(2, QtCore.Qt.Horizontal, "Status") self.sourceModel.setHeaderData(3, QtCore.Qt.Horizontal, "Type") self.sourceModel.setHeaderData(4, QtCore.Qt.Horizontal, "Name") self.dlg.treeView_results.setColumnHidden(0, True) self.dlg.treeView_results.resizeColumnsToContents() self.dlg.treeView_results.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.dlg.treeView_results.setSelectionMode(1) self.dlg.treeView_results.selectRow(0) # sort on order of the keuzehulp, instead of status. #self.dlg.treeView_results.sortByColumn(2, QtCore.Qt.AscendingOrder) self.dlg.treeView_results.sortByColumn(0, QtCore.Qt.AscendingOrder) QApplication.restoreOverrideCursor() self.dlg.show()
class ListWidget(EditorWidget): widgettype = 'List' def __init__(self, *args, **kwargs): super(ListWidget, self).__init__(*args, **kwargs) self.listmodel = QStandardItemModel() self._bindvalue = None def createWidget(self, parent): return QComboBox(parent) def _buildfromlist(self, widget, listconfig): items = listconfig['items'] for item in items: parts = item.split(';') data = parts[0] try: desc = parts[1] except IndexError: desc = data try: path = parts[2] path = path.strip() icon = QIcon(path) except: icon = QIcon() item = QStandardItem(desc) item.setData(data, Qt.UserRole) item.setIcon(icon) self.listmodel.appendRow(item) def _buildfromlayer(self, widget, layerconfig): layername = layerconfig['layer'] keyfield = layerconfig['key'] valuefield = layerconfig['value'] filterexp = layerconfig.get('filter', None) try: layer = utils.layer_by_name(layername) except IndexError: roam.utils.warning( "Can't find layer {} in project".format(layername)) return keyfieldindex = layer.fields().lookupField(keyfield) valuefieldindex = layer.fields().lookupField(valuefield) if keyfieldindex == -1 or valuefieldindex == -1: roam.utils.warning(f"Can't find key or value column for widget " f"Id: {self.id} " f"Layer: {layername} " f"Key: {keyfield} - {keyfieldindex} " f"Value: {valuefield} - {valuefieldindex} ") return if self.allownulls: item = QStandardItem('(no selection)') item.setData(None, Qt.UserRole) self.listmodel.appendRow(item) fields = [keyfieldindex, valuefieldindex] iconfieldindex = layer.fields().lookupField('icon') if iconfieldindex > -1: fields.append("icon") if not filterexp and valuefieldindex == keyfieldindex and iconfieldindex == -1: values = layer.uniqueValues(keyfieldindex) values = sorted(values) for value in values: value = nullconvert(value) item = QStandardItem(value) item.setData(value, Qt.UserRole) self.listmodel.appendRow(item) return features = roam.api.utils.search_layer(layer, filterexp, fields, with_geometry=False) # Sort the fields based on value field features = sorted(features, key=lambda f: f[keyfield]) for feature in features: keyvalue = nullconvert(feature[keyfieldindex]) valuvalue = nullconvert(feature[valuefield]) try: path = feature["icon"] icon = QIcon(path) except KeyError: icon = QIcon() item = QStandardItem(keyvalue) item.setData(str(valuvalue), Qt.UserRole) item.setIcon(icon) self.listmodel.appendRow(item) def initWidget(self, widget, config): if widget.isEditable(): widget.editTextChanged.connect(self.emitvaluechanged) widget.currentIndexChanged.connect(self.emitvaluechanged) widget.setModel(self.listmodel) widget.showPopup = self.showpopup widget.setIconSize(QSize(24, 24)) widget.setStyleSheet( "QComboBox::drop-down {border-width: 0px;} QComboBox::down-arrow {image: url(noimg); border-width: 0px;}" ) widget.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength) def showpopup(self): if self.listmodel.rowCount() == 0: return self.largewidgetrequest.emit( BigListWidget, self.widget.currentIndex(), self._biglistitem, dict(model=self.listmodel, label=self.labeltext)) def updatefromconfig(self): super(ListWidget, self).updatefromconfig() self.listmodel.clear() if 'list' in self.config: listconfig = self.config['list'] self._buildfromlist(self.widget, listconfig) elif 'layer' in self.config: layerconfig = self.config['layer'] self._buildfromlayer(self.widget, layerconfig) super(ListWidget, self).endupdatefromconfig() @property def allownulls(self): return self.config.get('allownull', False) def validate(self, *args): if (not self.widget.currentText() == '' and not self.widget.currentText() == "(no selection)"): return True else: return False def _biglistitem(self, index): self.widget.setCurrentIndex(index.row()) def setvalue(self, value): self._bindvalue = value index = self.widget.findData(value) self.widget.setCurrentIndex(index) if index == -1 and self.widget.isEditable(): if value is None and not self.config['allownull']: return self.widget.addItem(str(value)) index = self.widget.count() - 1 self.widget.setCurrentIndex(index) def value(self): index = self.widget.currentIndex() value = self.widget.itemData(index) text = self.widget.currentText() if value is None and self.widget.isEditable( ) and not text == '(no selection)': return self.widget.currentText() return value
class DialogExportData(QDialog, DIALOG_UI): on_result = pyqtSignal( bool) # whether the tool was run successfully or not ValidExtensions = ['xtf', 'itf', 'gml', 'xml'] current_row_schema = 0 def __init__(self, iface, qgis_utils, conn_manager, context): QDialog.__init__(self) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.iface = iface self.conn_manager = conn_manager self.db_source = context.get_db_sources()[0] self.db = self.conn_manager.get_db_connector_from_source( self.db_source) self.qgis_utils = qgis_utils self.logger = Logger() self.java_utils = JavaUtils() self.java_utils.download_java_completed.connect( self.download_java_complete) self.java_utils.download_java_progress_changed.connect( self.download_java_progress_change) self.base_configuration = BaseConfiguration() self.ilicache = IliCache(self.base_configuration) self.ilicache.refresh() self._dbs_supported = ConfigDbSupported() self._running_tool = False # There may be 1 case where we need to emit a db_connection_changed from the Export Data dialog: # 1) Connection Settings was opened and the DB conn was changed. self._db_was_changed = False # To postpone calling refresh gui until we close this dialog instead of settings # Similarly, we could call a refresh on layers and relations cache in 1 case: # 1) If the ED dialog was called for the COLLECTED source: opening Connection Settings and changing the DB # connection. self._schedule_layers_and_relations_refresh = False # We need bar definition above calling clear_messages self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) self.xtf_file_browse_button.clicked.connect( make_save_file_selector( self.xtf_file_line_edit, title=QCoreApplication.translate("DialogExportData", "Save in XTF Transfer File"), file_filter=QCoreApplication.translate( "DialogExportData", "XTF Transfer File (*.xtf);;Interlis 1 Transfer File (*.itf);;XML (*.xml);;GML (*.gml)" ), extension='.xtf', extensions=['.' + ext for ext in self.ValidExtensions])) self.xtf_file_browse_button.clicked.connect( self.xtf_browser_opened_to_true) self.xtf_browser_was_opened = False self.validators = Validators() fileValidator = FileValidator( pattern=['*.' + ext for ext in self.ValidExtensions], allow_non_existing=True) self.xtf_file_line_edit.setPlaceholderText( QCoreApplication.translate("DialogExportData", "[Name of the XTF to be created]")) self.xtf_file_line_edit.setValidator(fileValidator) self.xtf_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.xtf_file_line_edit.textChanged.connect( self.xtf_browser_opened_to_false) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) self.connection_setting_button.clicked.connect(self.show_settings) self.connection_setting_button.setText( QCoreApplication.translate("DialogExportData", "Connection Settings")) # LOG self.log_config.setTitle( QCoreApplication.translate("DialogExportData", "Show log")) self.log_config.setFlat(True) self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self._accept_button = self.buttonBox.addButton( QCoreApplication.translate("DialogExportData", "Export data"), QDialogButtonBox.AcceptRole) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.show_help) self.update_connection_info() self.update_model_names() self.restore_configuration() def update_connection_info(self): db_description = self.db.get_description_conn_string() if db_description: self.db_connect_label.setText(db_description) self.db_connect_label.setToolTip(self.db.get_display_conn_string()) self._accept_button.setEnabled(True) else: self.db_connect_label.setText( QCoreApplication.translate("DialogExportData", "The database is not defined!")) self.db_connect_label.setToolTip('') self._accept_button.setEnabled(False) def update_model_names(self): self.export_models_qmodel = QStandardItemModel() model_names = self.db.get_models() if model_names: for model_name in model_names: if model_name not in LADMNames.DEFAULT_HIDDEN_MODELS: item = QStandardItem(model_name) item.setCheckable(False) item.setEditable(False) self.export_models_qmodel.appendRow(item) self.export_models_list_view.setModel(self.export_models_qmodel) def reject(self): if self._running_tool: QMessageBox.information( self, QCoreApplication.translate("DialogExportData", "Warning"), QCoreApplication.translate( "DialogExportData", "The Export Data tool is still running. Please wait until it finishes." )) else: self.close_dialog() def close_dialog(self): """ We use this method to be safe when emitting the db_connection_changed, otherwise we could trigger slots that unload the plugin, destroying dialogs and thus, leading to crashes. """ if self._schedule_layers_and_relations_refresh: self.conn_manager.db_connection_changed.connect( self.qgis_utils.cache_layers_and_relations) if self._db_was_changed: # If the db was changed, it implies it complies with ladm_col, hence the second parameter self.conn_manager.db_connection_changed.emit( self.db, True, self.db_source) if self._schedule_layers_and_relations_refresh: self.conn_manager.db_connection_changed.disconnect( self.qgis_utils.cache_layers_and_relations) self.logger.info(__name__, "Dialog closed.") self.done(QDialog.Accepted) def get_ili_models(self): ili_models = list() for index in range(self.export_models_qmodel.rowCount()): item = self.export_models_qmodel.item(index) ili_models.append(item.text()) return ili_models def show_settings(self): # We only need those tabs related to Model Baker/ili2db operations dlg = SettingsDialog(qgis_utils=self.qgis_utils, conn_manager=self.conn_manager) dlg.set_db_source(self.db_source) dlg.set_tab_pages_list( [SETTINGS_CONNECTION_TAB_INDEX, SETTINGS_MODELS_TAB_INDEX]) # Connect signals (DBUtils, QgisUtils) dlg.db_connection_changed.connect(self.db_connection_changed) if self.db_source == COLLECTED_DB_SOURCE: self._schedule_layers_and_relations_refresh = True dlg.set_action_type(EnumDbActionType.EXPORT) if dlg.exec_(): self.db = dlg.get_db_connection() self.update_model_names() self.update_connection_info() def db_connection_changed(self, db, ladm_col_db, db_source): self._db_was_changed = True self.clear_messages() def accepted(self): self._running_tool = True self.bar.clearWidgets() java_home_set = self.java_utils.set_java_home() if not java_home_set: message_java = QCoreApplication.translate( "DialogExportData", """Configuring Java {}...""").format(JAVA_REQUIRED_VERSION) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(message_java) self.java_utils.get_java_on_demand() self.disable() return configuration = self.update_configuration() if not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable: self._running_tool = False message_error = QCoreApplication.translate( "DialogExportData", "Please set a valid XTF file before exporting data.") self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.xtf_file_line_edit.setFocus() return if not self.get_ili_models(): self._running_tool = False message_error = QCoreApplication.translate( "DialogExportData", "Please set a valid schema to export. This schema does not have information to export." ) self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.export_models_list_view.setFocus() return if not configuration.iliexportmodels: self._running_tool = False message_error = QCoreApplication.translate( "DialogExportData", "Please set a model before exporting data.") self.txtStdout.setText(message_error) self.show_message(message_error, Qgis.Warning) self.export_models_list_view.setFocus() return # If xtf browser was opened and the file exists, the user already chose # to overwrite the file if os.path.isfile(self.xtf_file_line_edit.text().strip() ) and not self.xtf_browser_was_opened: self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.msg.setText( QCoreApplication.translate( "DialogExportData", "{filename} already exists.\nDo you want to replace it?"). format(filename=os.path.basename( self.xtf_file_line_edit.text().strip()))) self.msg.setWindowTitle( QCoreApplication.translate("DialogExportData", "Save in XTF Transfer File")) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box = self.msg.exec_() if msg_box == QMessageBox.No: self._running_tool = False return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() exporter = iliexporter.Exporter() db_factory = self._dbs_supported.get_db_factory(self.db.engine) exporter.tool = db_factory.get_mbaker_db_ili_mode() exporter.configuration = configuration self.save_configuration(configuration) exporter.stdout.connect(self.print_info) exporter.stderr.connect(self.on_stderr) exporter.process_started.connect(self.on_process_started) exporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if exporter.run() != iliexporter.Exporter.SUCCESS: self._running_tool = False self.show_message( QCoreApplication.translate( "DialogExportData", "An error occurred when exporting the data. For more information see the log..." ), Qgis.Warning) self.on_result.emit( False ) # Inform other classes that the execution was not successful return except JavaNotFoundError: self._running_tool = False message_error_java = QCoreApplication.translate( "DialogExportData", "Java {} could not be found. You can configure the JAVA_HOME environment variable manually, restart QGIS and try again." ).format(JAVA_REQUIRED_VERSION) self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(message_error_java) self.show_message(message_error_java, Qgis.Warning) return self._running_tool = False self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) self.show_message( QCoreApplication.translate( "DialogExportData", "Export of the data was successfully completed."), Qgis.Success) self.on_result.emit( True) # Inform other classes that the execution was successful def download_java_complete(self): self.accepted() def download_java_progress_change(self, progress): self.progress_bar.setValue(progress / 2) if (progress % 20) == 0: self.txtStdout.append('...') def save_configuration(self, configuration): settings = QSettings() settings.setValue( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_export', configuration.xtffile) settings.setValue('Asistente-LADM_COL/QgisModelBaker/show_log', not self.log_config.isCollapsed()) def restore_configuration(self): settings = QSettings() self.xtf_file_line_edit.setText( settings.value( 'Asistente-LADM_COL/QgisModelBaker/ili2pg/xtffile_export')) # Show log value_show_log = settings.value( 'Asistente-LADM_COL/QgisModelBaker/show_log', False, type=bool) self.log_config.setCollapsed(not value_show_log) # set model repository # if there is no option by default use online model repository custom_model_is_checked = settings.value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', DEFAULT_USE_CUSTOM_MODELS, type=bool) if custom_model_is_checked: self.custom_model_directories = settings.value( 'Asistente-LADM_COL/models/custom_models', DEFAULT_MODELS_DIR) def update_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ db_factory = self._dbs_supported.get_db_factory(self.db.engine) configuration = ExportConfiguration() db_factory.set_ili2db_configuration_params(self.db.dict_conn_params, configuration) configuration.xtffile = self.xtf_file_line_edit.text().strip() full_java_exe_path = JavaUtils.get_full_java_exe_path() if full_java_exe_path: self.base_configuration.java_path = full_java_exe_path # User could have changed the default values self.use_local_models = QSettings().value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', DEFAULT_USE_CUSTOM_MODELS, type=bool) self.custom_model_directories = QSettings().value( 'Asistente-LADM_COL/models/custom_models', DEFAULT_MODELS_DIR) # Check custom model directories if self.use_local_models: if not self.custom_model_directories: self.base_configuration.custom_model_directories_enabled = False else: self.base_configuration.custom_model_directories = self.custom_model_directories self.base_configuration.custom_model_directories_enabled = True configuration.base_configuration = self.base_configuration if self.get_ili_models(): configuration.iliexportmodels = ';'.join(self.get_ili_models()) configuration.ilimodels = ';'.join(self.get_ili_models()) configuration.disable_validation = not QSettings().value( 'Asistente-LADM_COL/advanced_settings/validate_data_importing_exporting', True, bool) return configuration def print_info(self, text, text_color='#000000', clear=False): self.txtStdout.setTextColor(QColor(text_color)) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = '#004905' if exit_code == 0 else '#aa2222' self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append( QCoreApplication.translate("DialogExportData", "Finished ({})").format(exit_code)) if result == iliexporter.Exporter.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: self.enable() # Open log if self.log_config.isCollapsed(): self.log_config.setCollapsed(False) def advance_progress_bar_by_text(self, text): if text.strip() == 'Info: compile models...': self.progress_bar.setValue(50) QCoreApplication.processEvents() elif text.strip() == 'Info: process data...': self.progress_bar.setValue(55) QCoreApplication.processEvents() elif text.strip() == 'Info: first validation pass...': self.progress_bar.setValue(70) QCoreApplication.processEvents() elif text.strip() == 'Info: second validation pass...': self.progress_bar.setValue(85) QCoreApplication.processEvents() def clear_messages(self): self.bar.clearWidgets( ) # Remove previous messages before showing a new one self.txtStdout.clear() # Clear previous log messages self.progress_bar.setValue(0) # Initialize progress bar def show_help(self): self.qgis_utils.show_help("export_data") def disable(self): self.db_config.setEnabled(False) self.ili_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.db_config.setEnabled(True) self.ili_config.setEnabled(True) self.buttonBox.setEnabled(True) def show_message(self, message, level): if level == Qgis.Warning: self.enable() self.bar.clearWidgets( ) # Remove previous messages before showing a new one self.bar.pushMessage("Asistente LADM_COL", message, level, duration=0) def xtf_browser_opened_to_true(self): """ Slot. Sets a flag to true to eventually avoid asking a user whether to overwrite a file. """ self.xtf_browser_was_opened = True def xtf_browser_opened_to_false(self): """ Slot. Sets a flag to false to eventually ask a user whether to overwrite a file. """ self.xtf_browser_was_opened = False self.clear_messages()
class FKProperty(WIDGET, BASE): """ Editor to create/edit ForeignKey column property """ def __init__(self, parent, relation={}): """ :param parent: Owner of the form :type parent: QWidget :param relation: Dictionary holding fields used to build foreign key column *entity_relation - EntityRelation object, if its None then this is a new column else its an edit *fk_entities - entities used for ForeignKey selection *profile - current profile *entity - current entity you are creating column for. *column_name - name of the column :type form_field: dictionary """ QDialog.__init__(self, parent) self.setupUi(self) self._entity_relation = relation['form_fields']['entity_relation'] self.fk_entities = relation['fk_entities'] self.profile = relation['profile'] self.entity = relation['entity'] self.column_name = relation['column_name'] self.in_db = relation['form_fields']['in_db'] self._show_in_parent = relation['show_in_parent'] self._show_in_child = relation['show_in_child'] self.column_model = QStandardItemModel() self.lvDisplayCol.setModel(self.column_model) self.init_gui() def init_gui(self): """ Initializes form fields """ self.cboPrimaryEntity.currentIndexChanged.connect( \ self.load_entity_columns) self.load_fk_entities() if self._entity_relation: parent = self._entity_relation.parent.short_name parent_column = self._entity_relation.parent_column display_cols = self._entity_relation.display_cols self.cboPrimaryEntity.setCurrentIndex( \ self.cboPrimaryEntity.findText(parent)) self.cboPrimaryUKey.setCurrentIndex( \ self.cboPrimaryUKey.findText(parent_column)) self.show_display_cols(display_cols) # Disable controls if column exists in the database self.cboPrimaryEntity.setEnabled(not self.in_db) self.cboPrimaryUKey.setEnabled(not self.in_db) self.lvDisplayCol.setEnabled(not self.in_db) self.show_in_parent_chk.clicked.connect(self.on_show_in_parent_clicked) self.show_in_child_chk.clicked.connect(self.on_show_in_child_clicked) def on_show_in_parent_clicked(self): """ A slot raised when show in parent is clicked. :return: :rtype: """ if self.show_in_parent_chk.isChecked(): self.show_in_child_chk.setChecked(False) self._show_in_parent = True def on_show_in_child_clicked(self): """ A slot raised when show in child is clicked. :return: :rtype: """ if self.show_in_child_chk.isChecked(): self.show_in_parent_chk.setChecked(False) self._show_in_child = True def show_in_parent(self): """ Returns show in parent. :return: Returns show in parent. :rtype: Boolean """ return self._show_in_parent def show_in_child(self): """ Returns show in child. :return: Returns show in child. :rtype: Boolean """ return self._show_in_child def show_display_cols(self, display_cols): """ checks previously selected display columns """ for row in range(self.column_model.rowCount()): if str(self.column_model.item(row).text()) in display_cols: self.column_model.item(row).setCheckState(Qt.Checked) def load_fk_entities(self): """ populates combobox with entities to select primary entity for the foreign key """ self.cboPrimaryEntity.clear() self.cboPrimaryEntity.insertItems( 0, [name[0] for name in self.fk_entities]) self.cboPrimaryEntity.setCurrentIndex(0) def entity_columns(self): """ returns: A list used to select child entity column when building a foreign key rtype: list """ index = self.cboPrimaryEntity.currentIndex() entity_columns = \ [column for column in self.fk_entities[index][1].columns.items()] column_names = [column[0] for column in entity_columns] return column_names def fk_display_columns(self): """ returns: A list of columns used to select display columns in foreign key rtype: list """ index = self.cboPrimaryEntity.currentIndex() entity_columns = \ [column for column in self.fk_entities[index][1].columns.items()] columns = [column[0] for column in entity_columns \ if column[1].TYPE_INFO != 'SERIAL'] return columns def load_entity_columns(self): """ """ columns = self.entity_columns() self.populate_column_combobox(columns) disp_columns = self.fk_display_columns() self.populate_column_listview(disp_columns) def populate_column_combobox(self, columns): """ Populate combobox with column names param columns: List of entity columns to select your primary unique column for the foreign key type columns: list """ self.cboPrimaryUKey.clear() self.cboPrimaryUKey.insertItems(0, columns) def populate_column_listview(self, columns): """ Populates list view with columns used in selecting display columns for foreign key param columns: A list of column names type columns: list """ self.column_model.clear() for column in columns: item = QStandardItem(column) item.setCheckable(True) self.column_model.appendRow(item) def add_values(self): """ Construct an EntityRelation instance from form fields """ er_fields = {} er_fields['parent'] = str(self.cboPrimaryEntity.currentText()) er_fields['parent_column'] = str(self.cboPrimaryUKey.currentText()) er_fields['display_columns'] = self.display_columns() er_fields['child'] = self.entity er_fields['child_column'] = self.column_name self._entity_relation = EntityRelation(self.profile, **er_fields) def display_columns(self): """ Scans StandardItemModel for display columns, and returns a list of selected/checked columns for display in foreign key rtype: list """ return [str(self.column_model.item(row).text()) \ for row in range(self.column_model.rowCount()) \ if self.column_model.item(row).checkState() == Qt.Checked] def entity_relation(self): """ returns: entity relation instance rtype: EntityRelation """ return self._entity_relation def accept(self): self.add_values() self.done(1) def reject(self): self.done(0)
class PosiviewProperties(QgsOptionsDialogBase, Ui_PosiviewPropertiesBase): ''' GUI class classdocs for the Configuration dialog ''' applyChanges = pyqtSignal(dict) def __init__(self, project, parent=None): ''' Setup dialog widgets with the project properties ''' super(PosiviewProperties, self).__init__("PosiViewProperties", parent) self.setupUi(self) self.groupBox_6.hide() self.initOptionsBase(False) self.restoreOptionsBaseUi() self.comboBoxParser.addItems(PARSERS) self.comboBoxProviderType.addItems(DEVICE_TYPES) self.project = project self.projectProperties = project.properties() self.mToolButtonLoad.setDefaultAction(self.actionLoadConfiguration) self.mToolButtonSave.setDefaultAction(self.actionSaveConfiguration) self.mobileModel = QStringListModel() self.mobileListModel = QStringListModel() self.mMobileListView.setModel(self.mobileListModel) self.mobileProviderModel = QStandardItemModel() self.mobileProviderModel.setHorizontalHeaderLabels( ('Provider', 'Filter')) self.mMobileProviderTableView.setModel(self.mobileProviderModel) self.providerListModel = QStringListModel() self.mDataProviderListView.setModel(self.providerListModel) self.comboBoxProviders.setModel(self.providerListModel) self.setupModelData(self.projectProperties) self.setupGeneralData(self.projectProperties) def setupModelData(self, properties): self.mobileListModel.setStringList(sorted( properties['Mobiles'].keys())) self.providerListModel.setStringList( sorted(properties['Provider'].keys())) def setupGeneralData(self, properties): self.lineEditCruise.setText(properties['Mission']['cruise']) self.lineEditDive.setText(properties['Mission']['dive']) self.lineEditStation.setText(properties['Mission']['station']) self.lineEditRecorderPath.setText(properties['RecorderPath']) self.checkBoxAutoRecording.setChecked(properties['AutoRecord']) self.spinBoxNotifyDuration.setValue(properties['NotifyDuration']) self.checkBoxUtcClock.setChecked(properties['ShowUtcClock']) self.checkBoxWithSuffix.setChecked(properties['DefaultFormat'] & 4) self.comboBoxDefaultPositionFormat.setCurrentIndex( (properties['DefaultFormat']) & 3) def updateGeneralData(self): self.projectProperties['Mission']['cruise'] = self.lineEditCruise.text( ) self.projectProperties['Mission']['dive'] = self.lineEditDive.text() self.projectProperties['Mission'][ 'station'] = self.lineEditStation.text() self.projectProperties[ 'RecorderPath'] = self.lineEditRecorderPath.text() self.projectProperties[ 'AutoRecord'] = self.checkBoxAutoRecording.isChecked() self.projectProperties[ 'NotifyDuration'] = self.spinBoxNotifyDuration.value() self.projectProperties[ 'ShowUtcClock'] = self.checkBoxUtcClock.isChecked() self.projectProperties[ 'DefaultFormat'] = self.comboBoxDefaultPositionFormat.currentIndex( ) if self.checkBoxWithSuffix.isChecked(): self.projectProperties['DefaultFormat'] |= 4 def getColor(self, value): try: return QColor.fromRgba(int(value)) except ValueError: return QColor(value) @pyqtSlot(QAbstractButton, name='on_buttonBox_clicked') def onButtonBoxClicked(self, button): role = self.buttonBox.buttonRole(button) if role == QDialogButtonBox.ApplyRole or role == QDialogButtonBox.AcceptRole: self.updateGeneralData() self.applyChanges.emit(self.projectProperties) @pyqtSlot(name='on_actionSaveConfiguration_triggered') def onActionSaveConfigurationTriggered(self): ''' Save the current configuration ''' fn, __ = QFileDialog.getSaveFileName(None, 'Save PosiView configuration', '', 'Configuration (*.ini *.conf)') if fn: if not os.path.splitext(fn)[1]: fn += u'.conf' self.project.store(fn) @pyqtSlot(name='on_actionLoadConfiguration_triggered') def onActionLoadConfigurationTriggered(self): ''' Load configuration from file ''' fn, __ = QFileDialog.getOpenFileName(None, 'Save PosiView configuration', '', 'Configuration (*.ini *.conf)') self.projectProperties = self.project.read(fn) self.setupModelData(self.projectProperties) self.setupGeneralData(self.projectProperties) @pyqtSlot(QModelIndex, name='on_mMobileListView_clicked') def editMobile(self, index): ''' Populate the widgets with the selected mobiles properties ''' if index.isValid(): self.populateMobileWidgets(index) @pyqtSlot(str, name='on_comboBoxMobileType_currentIndexChanged') def mobileTypeChanged(self, mType): if mType == 'SHAPE': self.lineEditMobileShape.setEnabled(True) else: self.lineEditMobileShape.setEnabled(False) @pyqtSlot(QModelIndex, name='on_mMobileListView_activated') def activated(self, index): pass @pyqtSlot(name='on_toolButtonAddMobile_clicked') def addMobile(self): self.mobileListModel.insertRow(self.mobileListModel.rowCount()) index = self.mobileListModel.index(self.mobileListModel.rowCount() - 1) self.lineEditMobileName.setText('NewMobile') self.mobileListModel.setData(index, 'NewMobile', Qt.DisplayRole) self.mMobileListView.setCurrentIndex(index) self.applyMobile() @pyqtSlot(name='on_pushButtonApplyMobile_clicked') def applyMobile(self): index = self.mMobileListView.currentIndex() if index.isValid() and not self.lineEditMobileName.text() == '': mobile = dict() mobile['Name'] = self.lineEditMobileName.text() mobile['type'] = self.comboBoxMobileType.currentText() try: t = eval(self.lineEditMobileShape.text()) if t.__class__ is tuple or t.__class__ is dict: mobile['shape'] = t except SyntaxError: mobile['shape'] = ((0.0, -0.5), (0.3, 0.5), (0.0, 0.2), (-0.5, 0.5)) mobile['length'] = self.doubleSpinBoxMobileLength.value() mobile['width'] = self.doubleSpinBoxMobileWidth.value() mobile['defaultIcon'] = self.checkBoxDefaultIcon.isChecked() mobile['defaultIconFilled'] = self.checkBoxDefIconFilled.isChecked( ) mobile['offsetX'] = self.doubleSpinBoxXOffset.value() mobile['offsetY'] = self.doubleSpinBoxYOffset.value() mobile['zValue'] = self.spinBoxZValue.value() mobile['color'] = self.mColorButtonMobileColor.color().rgba() mobile['fillColor'] = self.mColorButtonMobileFillColor.color( ).rgba() mobile['timeout'] = self.spinBoxMobileTimeout.value() * 1000 mobile['nofixNotify'] = self.spinBoxMobileNotification.value() mobile['fadeOut'] = self.checkBoxFadeOut.isChecked() mobile['trackLength'] = self.spinBoxTrackLength.value() mobile['trackColor'] = self.mColorButtonMobileTrackColor.color( ).rgba() mobile['showLabel'] = self.checkBoxShowLabel.isChecked() provs = dict() for r in range(self.mobileProviderModel.rowCount()): try: fil = int( self.mobileProviderModel.item(r, 1).data(Qt.DisplayRole)) except Exception: fil = self.mobileProviderModel.item(r, 1).data(Qt.DisplayRole) if not fil: fil = None provs[self.mobileProviderModel.item(r, 0).data( Qt.DisplayRole)] = fil mobile['provider'] = provs currName = self.mobileListModel.data(index, Qt.DisplayRole) if not currName == mobile['Name']: del self.projectProperties['Mobiles'][currName] self.mobileListModel.setData(index, mobile['Name'], Qt.DisplayRole) self.projectProperties['Mobiles'][mobile['Name']] = mobile def populateMobileWidgets(self, index): mobile = self.projectProperties['Mobiles'][self.mobileListModel.data( index, Qt.DisplayRole)] self.lineEditMobileName.setText(mobile.get('Name')) self.comboBoxMobileType.setCurrentIndex( self.comboBoxMobileType.findText( mobile.setdefault('type', 'BOX').upper())) if mobile['type'] == 'SHAPE': self.lineEditMobileShape.setText(str(mobile['shape'])) self.lineEditMobileShape.setEnabled(True) self.doubleSpinBoxXOffset.setEnabled(True) self.doubleSpinBoxYOffset.setEnabled(True) else: self.lineEditMobileShape.setEnabled(False) self.doubleSpinBoxXOffset.setEnabled(False) self.doubleSpinBoxYOffset.setEnabled(False) self.lineEditMobileShape.clear() self.doubleSpinBoxMobileLength.setValue(mobile.get('length', 20.0)) self.doubleSpinBoxMobileWidth.setValue(mobile.get('width', 5.0)) self.checkBoxDefaultIcon.setChecked(mobile.get('defaultIcon', True)) self.checkBoxDefIconFilled.setChecked( mobile.get('defaultIconFilled', False)) self.doubleSpinBoxXOffset.setValue(mobile.get('offsetX', 0.0)) self.doubleSpinBoxYOffset.setValue(mobile.get('offsetY', 0.0)) self.spinBoxZValue.setValue(mobile.get('zValue', 100)) self.mColorButtonMobileColor.setColor( self.getColor(mobile.get('color', 'black'))) self.mColorButtonMobileFillColor.setColor( self.getColor(mobile.get('fillColor', 'green'))) self.spinBoxMobileTimeout.setValue(mobile.get('timeout', 3000) / 1000) self.spinBoxMobileNotification.setValue(mobile.get('nofixNotify', 0)) self.checkBoxFadeOut.setChecked(mobile.get('fadeOut', False)) self.spinBoxTrackLength.setValue(mobile.get('trackLength', 100)) self.mColorButtonMobileTrackColor.setColor( self.getColor(mobile.get('trackColor', 'green'))) self.checkBoxShowLabel.setChecked(mobile.get('showLabel', False)) r = 0 self.mobileProviderModel.removeRows( 0, self.mobileProviderModel.rowCount()) if 'provider' in mobile: for k, v in list(mobile['provider'].items()): prov = QStandardItem(k) val = QStandardItem(str(v)) self.mobileProviderModel.setItem(r, 0, prov) self.mobileProviderModel.setItem(r, 1, val) r += 1 @pyqtSlot(name='on_toolButtonRemoveMobile_clicked') def removeMobile(self): idx = self.mMobileListView.currentIndex() if idx.isValid(): self.projectProperties['Mobiles'].pop( self.mobileListModel.data(idx, Qt.DisplayRole)) self.mobileListModel.removeRows(idx.row(), 1) idx = self.mMobileListView.currentIndex() if idx.isValid(): self.populateMobileWidgets(idx) @pyqtSlot(name='on_toolButtonRefreshMobileProvider_clicked') def refreshMobileProvider(self): prov = self.comboBoxProviders.currentText() if prov == '': return fil = None if self.lineEditProviderFilter.text() != '': fil = self.lineEditProviderFilter.text() items = self.mobileProviderModel.findItems(prov, Qt.MatchExactly, 0) if items: for item in items: self.mobileProviderModel.setItem(item.row(), 1, QStandardItem(fil)) else: self.mobileProviderModel.appendRow( [QStandardItem(prov), QStandardItem(fil)]) @pyqtSlot(name='on_toolButtonRemoveMobileProvider_clicked') def removeMobileProvider(self): idx = self.mMobileProviderTableView.currentIndex() if idx.isValid(): self.mobileProviderModel.removeRow(idx.row()) @pyqtSlot(name='on_pushButtonApplyDataProvider_clicked') def applyDataProvider(self): index = self.mDataProviderListView.currentIndex() if index.isValid() and not self.lineEditProviderName.text() == '': provider = dict() provider['Name'] = self.lineEditProviderName.text() provider['DataDeviceType'] = self.comboBoxProviderType.currentText( ) if provider['DataDeviceType'] in NETWORK_TYPES: provider['Host'] = self.lineEditProviderHostName.text() provider['Port'] = self.spinBoxProviderPort.value() provider['Parser'] = self.comboBoxParser.currentText() currName = self.providerListModel.data(index, Qt.DisplayRole) if not currName == provider['Name']: del self.projectProperties['Provider'][currName] self.providerListModel.setData(index, provider['Name'], Qt.DisplayRole) self.projectProperties['Provider'][provider['Name']] = provider @pyqtSlot(QModelIndex, name='on_mDataProviderListView_clicked') def editDataProvider(self, index): ''' ''' if index.isValid(): self.populateDataProviderWidgets(index) def populateDataProviderWidgets(self, index): provider = self.projectProperties['Provider'][ self.providerListModel.data(index, Qt.DisplayRole)] self.lineEditProviderName.setText(provider.get('Name')) self.comboBoxProviderType.setCurrentIndex( self.comboBoxProviderType.findText( provider.setdefault('DataDeviceType', 'UDP').upper())) if provider['DataDeviceType'] in NETWORK_TYPES: self.stackedWidgetDataDevice.setCurrentIndex(0) self.lineEditProviderHostName.setText( provider.setdefault('Host', '0.0.0.0')) self.spinBoxProviderPort.setValue( int(provider.setdefault('Port', 2000))) self.comboBoxParser.setCurrentIndex( self.comboBoxParser.findText( provider.setdefault('Parser', 'NONE').upper())) @pyqtSlot(name='on_toolButtonAddDataProvider_clicked') def addDataProvider(self): self.providerListModel.insertRow(self.providerListModel.rowCount()) index = self.providerListModel.index( self.providerListModel.rowCount() - 1) self.lineEditProviderName.setText('NewDataProvider') self.providerListModel.setData(index, 'NewDataProvider', Qt.DisplayRole) self.mDataProviderListView.setCurrentIndex(index) self.applyDataProvider() @pyqtSlot(name='on_toolButtonRemoveDataProvider_clicked') def removeDataProvider(self): idx = self.mDataProviderListView.currentIndex() if idx.isValid(): self.projectProperties['Provider'].pop( self.providerListModel.data(idx, Qt.DisplayRole)) self.providerListModel.removeRows(idx.row(), 1) idx = self.mDataProviderListView.currentIndex() if idx.isValid(): self.populateDataProviderWidgets(idx) @pyqtSlot(name='on_toolButtonSelectLogPath_clicked') def selectRecorderPath(self): path = QFileDialog.getExistingDirectory( self, self.tr('Select Recorder Path'), self.lineEditRecorderPath.text(), QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if path != '': self.lineEditRecorderPath.setText(path) @pyqtSlot(QPoint, name='on_lineEditMobileShape_customContextMenuRequested') def mobileShapeContextMenu(self, pos): menu = QMenu(self.lineEditMobileShape) vesselAction = menu.addAction(self.tr('Vessel')) rovAction = menu.addAction(self.tr('ROV')) auvAction = menu.addAction(self.tr('AUV')) arrowAction = menu.addAction(self.tr('Arrow')) selectedAction = menu.exec_(self.lineEditMobileShape.mapToGlobal(pos)) if selectedAction == vesselAction: self.lineEditMobileShape.setText( u'((0, -0.5), (0.5, -0.3), (0.5, 0.5), (-0.5, 0.5), (-0.5, -0.3))' ) elif selectedAction == rovAction: self.lineEditMobileShape.setText( u'((0.3, -0.5), (0.5, -0.3), (0.5, 0.5), (-0.5, 0.5), (-0.5, -0.3), (-0.3, -0.5))' ) elif selectedAction == auvAction: self.lineEditMobileShape.setText( u'((0, -0.5), (0.4, -0.3), (0.5, -0.3), (0.5, -0.2), (0.4, -0.2), (0.4, 0.3), (0.5, 0.3), (0.5, 0.4), (0.4, 0.4), (0.0, 0.5), \ (-0.4, 0.4), (-0.5, 0.4), (-0.5, 0.3), (-0.4, 0.3), (-0.4, -0.2), (-0.5, -0.2), (-0.5, -0.3), (-0.4, -0.3))' ) elif selectedAction == arrowAction: self.lineEditMobileShape.setText( u'((0, -0.5), (0.5, 0.5), (0, 0), (-0.5, 0.5))') @pyqtSlot(name='on_buttonBox_helpRequested') def showHelp(self): """Display application help to the user.""" help_file = os.path.join( os.path.split(os.path.dirname(__file__))[0], 'help', 'index.html') QDesktopServices.openUrl(QUrl.fromLocalFile(help_file))
class ModelAtrributesView(QListView): """ Custom QListView implementation that displays checkable model attributes. """ def __init__(self, parent=None, dataModel=None): QListView.__init__(self, parent) self._dataModel = dataModel self._selectedDisplayMapping = OrderedDict() self._modelDisplayMapping = OrderedDict() self._attrModel = QStandardItemModel(self) def dataModel(self): """ Returns the data model instance. """ return self._dataModel def setDataModel(self, dataModel): """ Sets the data model. Should be a callable class rather than the class. instance. """ if callable(dataModel): self._dataModel = dataModel else: self._dataModel = dataModel.__class__ def modelDisplayMapping(self): """ Returns the column name and display name collection. """ return self._modelDisplayMapping def setModelDisplayMapping(self, dataMapping): """ Sets the mapping dictionary for the table object """ if dataMapping != None: self._modelDisplayMapping = dataMapping def load(self, sort=False): """ Load the model's attributes into the list view. """ if self._dataModel == None: return try: self._loadAttrs(self._dataModel.displayMapping(), sort) except AttributeError: # Ignore error if model does not contain # the displayMapping static method pass def load_mapping(self, mapping, sort=False): """ Load collection containing column name and corresponding display name. """ self._modelDisplayMapping = mapping self._loadAttrs(mapping, sort) def sort(self): """ Sorts display name in ascending order. """ self._attrModel.sort(0) def _loadAttrs(self, attrMapping, sort=False): """ Loads display mapping into the list view. Specify to sort display names in ascending order once items have been added to the model. """ self._attrModel.clear() self._attrModel.setColumnCount(2) for attrName, displayName in attrMapping.items(): # Exclude row ID in the list, other unique identifier attributes in the model can be used if attrName != "id": displayNameItem = QStandardItem(displayName) displayNameItem.setCheckable(True) attrNameItem = QStandardItem(attrName) self._attrModel.appendRow([displayNameItem, attrNameItem]) self.setModel(self._attrModel) if sort: self._attrModel.sort(0) def selectedMappings(self): """ Return a dictionary of field names and their corresponding display values. """ selectedAttrs = OrderedDict() for i in range(self._attrModel.rowCount()): displayNameItem = self._attrModel.item(i, 0) if displayNameItem.checkState() == Qt.Checked: attrNameItem = self._attrModel.item(i, 1) selectedAttrs[attrNameItem.text()] = displayNameItem.text() return selectedAttrs
class AbstractSTREnityListView(QListView): """ A widget for listing and selecting one or more STR entities. .. versionadded:: 1.7 """ def __init__(self, parent=None, **kwargs): super(AbstractSTREnityListView, self).__init__(parent) self._model = QStandardItemModel(self) self._model.setColumnCount(1) self.setModel(self._model) self.setEditTriggers(QAbstractItemView.NoEditTriggers) self._model.itemChanged.connect(self._on_item_changed) self._profile = kwargs.get('profile', None) self._social_tenure = kwargs.get('social_tenure', None) # Load appropriate entities to the view if not self._profile is None: self._load_profile_entities() # Load entities in the STR definition if not self._social_tenure is None: self._select_str_entities() def _on_item_changed(self, item): # Emit signals when an item has been (de)selected. To be # implemented by subclasses. pass @property def profile(self): """ :return: Returns the current profile object in the configuration. :rtype: Profile """ return self._profile @profile.setter def profile(self, profile): """ Sets the current profile object in the configuration. :param profile: Profile object. :type profile: Profile """ self._profile = profile self._load_profile_entities() @property def social_tenure(self): """ :return: Returns the profile's social tenure entity. :rtype: SocialTenure """ return self._social_tenure @social_tenure.setter def social_tenure(self, social_tenure): """ Set the social_tenure entity. :param social_tenure: A profile's social tenure entity. :type social_tenure: SocialTenure """ self._social_tenure = social_tenure self._select_str_entities() def _select_str_entities(self): """ Select the entities defined in the STR. E.g. parties for party entity and spatial units for spatial unit entity. Default implementation does nothing, to be implemented by subclasses. """ pass def _load_profile_entities(self): # Reset view self.clear() # Populate entity items in the view for e in self._profile.user_entities(): self._add_entity(e) def _add_entity(self, entity): # Add entity item to view item = QStandardItem( GuiUtils.get_icon('table.png'), entity.short_name ) item.setCheckable(True) item.setCheckState(Qt.Unchecked) self._model.appendRow(item) def select_entities(self, entities): """ Checks STR entities in the view and emit the entity_selected signal for each item selected. :param entities: Collection of STR entities. :type entities: list """ # Clear selection self.clear_selection() for e in entities: name = e.short_name self.select_entity(name) def selected_entities(self): """ :return: Returns a list of selected entity short names. :rtype: list """ selected_items = [] for i in range(self._model.rowCount()): item = self._model.item(i) if item.checkState() == Qt.Checked: selected_items.append(item.text()) return selected_items def clear(self): """ Remove all party items in the view. """ self._model.clear() self._model.setColumnCount(1) def clear_selection(self): """ Uncheck all items in the view. """ for i in range(self._model.rowCount()): item = self._model.item(i) if item.checkState() == Qt.Checked: item.setCheckState(Qt.Unchecked) def select_entity(self, name): """ Selects a party entity with the given short name. :param name: Entity short name :type name: str """ items = self._model.findItems(name) if len(items) > 0: item = items[0] if item.checkState() == Qt.Unchecked: item.setCheckState(Qt.Checked) def deselect_entity(self, name): """ Deselects an entity with the given short name. :param name: Entity short name :type name: str """ items = self._model.findItems(name) if len(items) > 0: item = items[0] if item.checkState() == Qt.Checked: item.setCheckState(Qt.Unchecked)
class MultipleInputDialog(BASE, WIDGET): def __init__(self, options, selectedoptions=None, datatype=None): super(MultipleInputDialog, self).__init__(None) self.setupUi(self) self.datatype = datatype self.model = None self.options = [] for i, option in enumerate(options): if option is None or isinstance(option, str): self.options.append((i, option)) else: self.options.append((option[0], option[1])) self.selectedoptions = selectedoptions or [] # Additional buttons self.btnSelectAll = QPushButton(self.tr('Select All')) self.buttonBox.addButton(self.btnSelectAll, QDialogButtonBox.ActionRole) self.btnClearSelection = QPushButton(self.tr('Clear Selection')) self.buttonBox.addButton(self.btnClearSelection, QDialogButtonBox.ActionRole) self.btnToggleSelection = QPushButton(self.tr('Toggle Selection')) self.buttonBox.addButton(self.btnToggleSelection, QDialogButtonBox.ActionRole) if self.datatype is not None: btnAddFile = QPushButton( QCoreApplication.translate("MultipleInputDialog", 'Add File(s)…')) btnAddFile.clicked.connect(self.addFiles) self.buttonBox.addButton(btnAddFile, QDialogButtonBox.ActionRole) self.btnSelectAll.clicked.connect(lambda: self.selectAll(True)) self.btnClearSelection.clicked.connect(lambda: self.selectAll(False)) self.btnToggleSelection.clicked.connect(self.toggleSelection) self.settings = QgsSettings() self.restoreGeometry( self.settings.value("/Processing/multipleInputDialogGeometry", QByteArray())) self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection) self.lstLayers.setDragDropMode(QAbstractItemView.InternalMove) self.populateList() self.finished.connect(self.saveWindowGeometry) def saveWindowGeometry(self): self.settings.setValue("/Processing/multipleInputDialogGeometry", self.saveGeometry()) def populateList(self): self.model = QStandardItemModel() for value, text in self.options: item = QStandardItem(text) item.setData(value, Qt.UserRole) item.setCheckState(Qt.Checked if value in self.selectedoptions else Qt.Unchecked) item.setCheckable(True) item.setDropEnabled(False) self.model.appendRow(item) # add extra options (e.g. manually added layers) for t in [o for o in self.selectedoptions if not isinstance(o, int)]: if isinstance(t, QgsProcessingModelChildParameterSource): item = QStandardItem(t.staticValue()) else: item = QStandardItem(t) item.setData(item.text(), Qt.UserRole) item.setCheckState(Qt.Checked) item.setCheckable(True) item.setDropEnabled(False) self.model.appendRow(item) self.lstLayers.setModel(self.model) def accept(self): self.selectedoptions = [] model = self.lstLayers.model() for i in range(model.rowCount()): item = model.item(i) if item.checkState() == Qt.Checked: self.selectedoptions.append(item.data(Qt.UserRole)) QDialog.accept(self) def reject(self): self.selectedoptions = None QDialog.reject(self) def getItemsToModify(self): items = [] if len(self.lstLayers.selectedIndexes()) > 1: for i in self.lstLayers.selectedIndexes(): items.append(self.model.itemFromIndex(i)) else: for i in range(self.model.rowCount()): items.append(self.model.item(i)) return items def selectAll(self, value): for item in self.getItemsToModify(): item.setCheckState(Qt.Checked if value else Qt.Unchecked) def toggleSelection(self): for item in self.getItemsToModify(): checked = item.checkState() == Qt.Checked item.setCheckState(Qt.Unchecked if checked else Qt.Checked) def getFileFilter(self, datatype): """ Returns a suitable file filter pattern for the specified parameter definition :param param: :return: """ if datatype == QgsProcessing.TypeRaster: return QgsProviderRegistry.instance().fileRasterFilters() elif datatype == QgsProcessing.TypeFile: return self.tr('All files (*.*)') else: exts = QgsVectorFileWriter.supportedFormatExtensions() for i in range(len(exts)): exts[i] = self.tr('{0} files (*.{1})').format( exts[i].upper(), exts[i].lower()) return self.tr('All files (*.*)') + ';;' + ';;'.join(exts) def addFiles(self): filter = self.getFileFilter(self.datatype) settings = QgsSettings() path = str(settings.value('/Processing/LastInputPath')) ret, selected_filter = QFileDialog.getOpenFileNames( self, self.tr('Select File(s)'), path, filter) if ret: files = list(ret) settings.setValue('/Processing/LastInputPath', os.path.dirname(str(files[0]))) for filename in files: item = QStandardItem(filename) item.setData(filename, Qt.UserRole) item.setCheckState(Qt.Checked) item.setCheckable(True) item.setDropEnabled(False) self.model.appendRow(item)
class ModelSelectionDialog(uicls, basecls): """Dialog for model selection.""" TABLE_LIMIT = 10 NAME_COLUMN_IDX = 1 def __init__(self, plugin_dock, parent=None): super().__init__(parent) self.setupUi(self) self.plugin_dock = plugin_dock self.communication = self.plugin_dock.communication self.current_user = self.plugin_dock.current_user self.threedi_api = self.plugin_dock.threedi_api self.organisations = self.plugin_dock.organisations self.threedi_models = None self.simulation_templates = None self.current_model = None self.current_model_cells = None self.current_model_breaches = None self.current_simulation_template = None self.cells_layer = None self.breaches_layer = None self.organisation = None self.model_is_loaded = False self.models_model = QStandardItemModel() self.models_tv.setModel(self.models_model) self.templates_model = QStandardItemModel() self.templates_tv.setModel(self.templates_model) self.pb_prev_page.clicked.connect(self.move_models_backward) self.pb_next_page.clicked.connect(self.move_models_forward) self.page_sbox.valueChanged.connect(self.fetch_3di_models) self.pb_load.clicked.connect(self.load_model) self.pb_cancel_load.clicked.connect(self.cancel_load_model) self.search_le.returnPressed.connect(self.search_model) self.models_tv.selectionModel().selectionChanged.connect( self.refresh_templates_list) self.templates_tv.selectionModel().selectionChanged.connect( self.toggle_load_model) self.populate_organisations() self.fetch_3di_models() def refresh_templates_list(self): """Refresh simulation templates list if any model is selected.""" selection_model = self.models_tv.selectionModel() self.templates_model.clear() self.templates_page_sbox.setMaximum(1) self.templates_page_sbox.setSuffix(" / 1") if selection_model.hasSelection(): self.fetch_simulation_templates() if self.templates_model.rowCount() > 0: row_idx = self.templates_model.index(0, 0) self.templates_tv.selectionModel().setCurrentIndex( row_idx, QItemSelectionModel.ClearAndSelect) self.toggle_load_model() def toggle_load_model(self): """Toggle load button if any model is selected.""" selection_model = self.templates_tv.selectionModel() if selection_model.hasSelection(): self.pb_load.setEnabled(True) else: self.pb_load.setDisabled(True) def move_models_backward(self): """Moving to the models previous results page.""" self.page_sbox.setValue(self.page_sbox.value() - 1) def move_models_forward(self): """Moving to the models next results page.""" self.page_sbox.setValue(self.page_sbox.value() + 1) def move_templates_backward(self): """Moving to the templates previous results page.""" self.templates_page_sbox.setValue(self.page_sbox.value() - 1) def move_templates_forward(self): """Moving to the templates next results page.""" self.templates_page_sbox.setValue(self.page_sbox.value() + 1) def populate_organisations(self): """Populating organisations list inside combo box.""" for org in self.organisations.values(): self.organisations_box.addItem(org.name, org) def fetch_3di_models(self): """Fetching 3Di models list.""" try: tc = ThreediCalls(self.threedi_api) offset = (self.page_sbox.value() - 1) * self.TABLE_LIMIT text = self.search_le.text() threedi_models, models_count = tc.fetch_3di_models_with_count( limit=self.TABLE_LIMIT, offset=offset, name_contains=text) pages_nr = ceil(models_count / self.TABLE_LIMIT) or 1 self.page_sbox.setMaximum(pages_nr) self.page_sbox.setSuffix(f" / {pages_nr}") self.models_model.clear() header = [ "ID", "Model", "Schematisation", "Revision", "Last updated", "Updated by" ] self.models_model.setHorizontalHeaderLabels(header) for sim_model in sorted(threedi_models, key=attrgetter("revision_commit_date"), reverse=True): id_item = QStandardItem(str(sim_model.id)) name_item = QStandardItem(sim_model.name) name_item.setData(sim_model, role=Qt.UserRole) schema_item = QStandardItem(sim_model.schematisation_name) rev_item = QStandardItem(sim_model.revision_number) last_updated_day = sim_model.revision_commit_date.split("T")[0] lu_datetime = QDateTime.fromString(last_updated_day, "yyyy-MM-dd") lu_item = QStandardItem(lu_datetime.toString("dd-MMMM-yyyy")) ub_item = QStandardItem(sim_model.user) self.models_model.appendRow([ id_item, name_item, schema_item, rev_item, lu_item, ub_item ]) self.threedi_models = threedi_models except ApiException as e: self.close() error_msg = extract_error_message(e) self.communication.show_error(error_msg) except Exception as e: self.close() error_msg = f"Error: {e}" self.communication.show_error(error_msg) def fetch_simulation_templates(self): """Fetching simulation templates list.""" try: tc = ThreediCalls(self.threedi_api) offset = (self.templates_page_sbox.value() - 1) * self.TABLE_LIMIT selected_model = self.get_selected_model() model_pk = selected_model.id templates, templates_count = tc.fetch_simulation_templates_with_count( model_pk, limit=self.TABLE_LIMIT, offset=offset) pages_nr = ceil(templates_count / self.TABLE_LIMIT) or 1 self.templates_page_sbox.setMaximum(pages_nr) self.templates_page_sbox.setSuffix(f" / {pages_nr}") self.templates_model.clear() header = ["Template ID", "Template name", "Creation date"] self.templates_model.setHorizontalHeaderLabels(header) for template in sorted(templates, key=attrgetter("id"), reverse=True): id_item = QStandardItem(str(template.id)) name_item = QStandardItem(template.name) name_item.setData(template, role=Qt.UserRole) creation_date = template.created.strftime( "%d-%m-%Y") if template.created else "" creation_date_item = QStandardItem(creation_date) self.templates_model.appendRow( [id_item, name_item, creation_date_item]) for i in range(len(header)): self.templates_tv.resizeColumnToContents(i) self.simulation_templates = templates except ApiException as e: error_msg = extract_error_message(e) self.communication.show_error(error_msg) except Exception as e: error_msg = f"Error: {e}" self.communication.show_error(error_msg) def search_model(self): """Method used for searching models with text typed withing search bar.""" self.page_sbox.valueChanged.disconnect(self.fetch_3di_models) self.page_sbox.setValue(1) self.page_sbox.valueChanged.connect(self.fetch_3di_models) self.fetch_3di_models() def load_cached_layers(self): """Loading cached layers into the map canvas.""" if self.current_model_cells is not None: self.cells_layer = QgsVectorLayer(self.current_model_cells, "cells", "ogr") set_named_style(self.cells_layer, "cells.qml") QgsProject.instance().addMapLayer(self.cells_layer, False) QgsProject.instance().layerTreeRoot().insertLayer( 0, self.cells_layer) self.cells_layer.setFlags(QgsMapLayer.Searchable | QgsMapLayer.Identifiable) if self.current_model_breaches is not None: self.breaches_layer = QgsVectorLayer(self.current_model_breaches, "breaches", "ogr") set_named_style(self.breaches_layer, "breaches.qml") QgsProject.instance().addMapLayer(self.breaches_layer, False) QgsProject.instance().layerTreeRoot().insertLayer( 0, self.breaches_layer) self.breaches_layer.setFlags(QgsMapLayer.Searchable | QgsMapLayer.Identifiable) if self.current_model_cells is not None: self.plugin_dock.iface.setActiveLayer(self.cells_layer) self.plugin_dock.iface.zoomToActiveLayer() def unload_cached_layers(self): """Removing model related vector layers from map canvas.""" try: if self.breaches_layer is not None: QgsProject.instance().removeMapLayer(self.breaches_layer) self.breaches_layer = None if self.cells_layer is not None: QgsProject.instance().removeMapLayer(self.cells_layer) self.cells_layer = None self.plugin_dock.iface.mapCanvas().refresh() except AttributeError: pass def load_model(self): """Loading selected model.""" index = self.models_tv.currentIndex() if index.isValid(): self.organisation = self.organisations_box.currentData() self.unload_cached_layers() current_row = index.row() name_item = self.models_model.item(current_row, self.NAME_COLUMN_IDX) self.current_model = name_item.data(Qt.UserRole) self.current_model_cells = self.get_cached_data("cells") self.current_model_breaches = self.get_cached_data("breaches") self.current_simulation_template = self.get_selected_template() self.load_cached_layers() self.model_is_loaded = True self.close() def cancel_load_model(self): """Cancel loading model.""" self.current_simulation_template = None self.model_is_loaded = False self.close() def get_selected_model(self): """Get currently selected model.""" index = self.models_tv.currentIndex() if index.isValid(): current_row = index.row() name_item = self.models_model.item(current_row, self.NAME_COLUMN_IDX) selected_model = name_item.data(Qt.UserRole) else: selected_model = None return selected_model def get_selected_template(self): """Get currently selected simulation template.""" index = self.templates_tv.currentIndex() if index.isValid(): current_row = index.row() name_item = self.templates_model.item(current_row, self.NAME_COLUMN_IDX) selected_template = name_item.data(Qt.UserRole) else: selected_template = None return selected_template def get_cached_data(self, geojson_name): """Get model data that should be cached.""" cached_file_path = None try: tc = ThreediCalls(self.threedi_api) model_id = self.current_model.id if geojson_name == "breaches": download = tc.fetch_3di_model_geojson_breaches_download( model_id) elif geojson_name == "cells": download = tc.fetch_3di_model_geojson_cells_download(model_id) else: return cached_file_path filename = f"{geojson_name}_{model_id}_{download.etag}.json" file_path = os.path.join(CACHE_PATH, filename) if not file_cached(file_path): get_download_file(download, file_path) cached_file_path = file_path self.communication.bar_info(f"Model {geojson_name} cached.") except ApiException as e: error_msg = extract_error_message(e) if "geojson file not found" in error_msg: pass else: self.communication.bar_error(error_msg) except Exception as e: logger.exception("Error when getting to-be-cached data") error_msg = f"Error: {e}" self.communication.bar_error(error_msg) return cached_file_path