def set_data(self, data): """Update the table data""" model = QStandardItemModel(len(data), 3) model.setHorizontalHeaderItem(0, QStandardItem("Duplicate Id")) model.setHorizontalHeaderItem(1, QStandardItem("Table")) model.setHorizontalHeaderItem(2, QStandardItem("Table")) row = 0 for (feat_id, rel1, rel2) in data: model.setData(model.index(row, 0), str(feat_id)) model.setData(model.index(row, 1), rel1) model.setData(model.index(row, 2), rel2) row += 1 self.tbl_dup_ids.setModel(model) self.tbl_dup_ids.setEditTriggers(QAbstractItemView.NoEditTriggers)
class FormWidget(ui_formwidget.Ui_Form, WidgetBase): def __init__(self, parent=None): super(FormWidget, self).__init__(parent) self.setupUi(self) self.form = None self.iconlabel.mouseReleaseEvent = self.change_icon self._currentwidgetid = '' self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.widgetmodel.rowsMoved.connect(self._widget_moved) self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=True) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.layerCombo.setModel(self.formlayers) self.useablewidgets.setModel(self.possiblewidgetsmodel) self.fieldList.setModel(self.fieldsmodel) self.userwidgets.setModel(self.widgetmodel) self.userwidgets.selectionModel().currentChanged.connect(self.load_widget) self.widgetmodel.rowsRemoved.connect(self.set_widget_config_state) self.widgetmodel.rowsInserted.connect(self.set_widget_config_state) self.widgetmodel.modelReset.connect(self.set_widget_config_state) self.addWidgetButton.pressed.connect(self.newwidget) self.addSectionButton.pressed.connect(self.add_section) self.removeWidgetButton.pressed.connect(self.removewidget) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.expressionButton.clicked.connect(self.opendefaultexpression) self.expressionButton_2.clicked.connect(self.opendefaultexpression_advanced) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) for item, data in defaultevents: self.defaultEventsCombo.addItem(item, data) self.loadwidgettypes() self.formLabelText.textChanged.connect(self.form_name_changed) self.newStyleCheck.stateChanged.connect(self.form_style_changed) self.layerCombo.currentIndexChanged.connect(self.layer_updated) # Gross but ok for now. self.blockWidgets = [ self.fieldList, self.nameText, self.sectionNameText, self.useablewidgets, self.hiddenCheck, self.requiredCheck, self.readonlyCombo, self.defaultEventsCombo, self.defaultvalueText, self.defaultLayerCombo, self.defaultFieldCombo, self.defaultValueExpression, self.savevalueCheck ] for widget in self.blockWidgets: self._connect_save_event(widget) self.blockWidgetSignels(True) self.useablewidgets.currentIndexChanged.connect(self.swapwidgetconfig) menu = QMenu("Field Actions") action = menu.addAction("Auto add all fields") action.triggered.connect(self.auto_add_fields) self.addWidgetButton.setMenu(menu) self.addWidgetButton.setPopupMode(QToolButton.MenuButtonPopup) self.defaultLayerCombo.layerChanged.connect(self.defaultFieldCombo.setLayer) self.addEvent.pressed.connect(self.addEventItem) self.btnDeleteForm.pressed.connect(ConfigEvents.deleteForm.emit) def _widget_moved(self, sourceParent, start, end, destParent, destRow): index = self.widgetmodel.index(destRow, 0, destParent) self.userwidgets.setCurrentIndex(index) def on_closing(self): self.blockWidgetSignels(True) def unload_project(self): self.blockWidgetSignels(True) def _connect_save_event(self, widget): if hasattr(widget, "textChanged"): widget.textChanged.connect(self._save_current_widget) if hasattr(widget, "currentIndexChanged"): widget.currentIndexChanged.connect(self._save_current_widget) if hasattr(widget, "stateChanged"): widget.stateChanged.connect(self._save_current_widget) def change_icon(self, *args): """ Change the icon for the form """ icon, _ = QFileDialog.getOpenFileName(self, "Select form icon image", filter="Images (*.png *.svg)") if not icon: return ext = os.path.splitext(icon)[1] shutil.copy(icon, os.path.join(self.form.folder, "icon" + ext)) self.set_icon(self.form.icon) self.treenode.emitDataChanged() def set_icon(self, path): """ Set the forms icon preview :param path: The path to icon. """ pixmap = QPixmap(path) w = self.iconlabel.width() h = self.iconlabel.height() self.iconlabel.setPixmap(pixmap.scaled(w, h, Qt.KeepAspectRatio)) def layer_updated(self, index): """ Called when the forms layer has changed. :param index: The index of the new layer. """ if not self.selected_layer: return self.updatefields(self.selected_layer) def form_style_changed(self, newstyle): """ Called when the form style has changed from label-above style to label-beside style. :param newstyle: True if to use the new label-above style forms. """ self.form.settings['newstyle'] = newstyle self.treenode.emitDataChanged() def form_name_changed(self, text): """ Called when the forms name has changed. Also updates the tree view to reflect the new name. :param text: The new text of the label. :return: """ self.form.settings['label'] = text self.treenode.emitDataChanged() def updatewidgetname(self, index): """ Update the widget name if the field has changed. Doesn't change the name if it has been user set already. :param index: index of the new field. """ # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def opendefaultexpression_advanced(self): """ Open the default expression builder for setting advanced default values based on QGIS Expressions. """ layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultValueExpression.text() dlg.setExpressionText(text) if dlg.exec_(): self.defaultValueExpression.setText(dlg.expressionText()) def opendefaultexpression(self): """ Open the default expression builder for setting default values based on QGIS Expressions. """ layer = self.form.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def formtabchanged(self, index): """ Called when the tab widget changes tab. Normally used to control when to render the form preview on demand. :param index: The index of the new tab. """ # Don't generate the form preview if we are not on the preview tab. if index == 3: self.generate_form_preview() def generate_form_preview(self): """ Create the form preview to show to the user. """ form = self.form.copy() form.settings['widgets'] = list(self.widgetmodel.widgets()) item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) featureform = FeatureForm.from_form(form, form.settings, None, {}) from roam import defaults defaultwidgets = form.widgetswithdefaults() layer = form.QGISLayer try: values = {} feature = next(layer.getFeatures()) defaultvalues = defaults.default_values(defaultwidgets, feature, layer) values.update(defaultvalues) featureform.bindvalues(values) except StopIteration: pass self.frame_2.layout().addWidget(featureform) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ widgets = self.widgetmodel.widgets() for widget in widgets: if 'field' in widget: yield widget['field'] def openformfolder(self, url): """ Open the form folder using the OS file manager. :param url: :return: """ QDesktopServices.openUrl(QUrl.fromLocalFile(self.form.folder)) def loadwidgettypes(self): """ Load all supported widgets into the combobox for the form designer. :return: """ self.useablewidgets.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(widgeticon(widgettype)) self.useablewidgets.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.useablewidgets.blockSignals(False) def set_widget_config_state(self, *args): """ Enable or disable the widget config section based on widget count :param args: Unused. :return: """ haswidgets = self.widgetmodel.rowCount() > 0 self.widgetConfigTabs.setEnabled(haswidgets) def add_section(self): """ Add a new widget section into the form. Widget sections can be used to group widgets on the form together. """ currentindex = self.userwidgets.currentIndex() widget = {"widget": "Section", "name": "default"} index = self.widgetmodel.addwidget(widget, currentindex.parent()) self.userwidgets.setCurrentIndex(index) def newwidget(self, field=None): """ Create a new widget. Tries to match the field type to the right kind of widget as a best guess. """ mapping = {QVariant.String: "Text", QVariant.Int: "Number", QVariant.Double: "Number(Double)", QVariant.ByteArray: "Image", QVariant.Date: "Date", QVariant.DateTime: "Date"} widget = {} if not field: field = self.fieldsmodel.index(0, 0).data(Qt.UserRole) if not field: return widget['field'] = field.name() else: widget['field'] = field.name() try: widget['widget'] = mapping[field.type()] except KeyError: widget['widget'] = 'Text' # Grab the first field. widget['name'] = field.name().replace("_", " ").title() currentindex = self.userwidgets.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.userwidgets.setCurrentIndex(index) def auto_add_fields(self): """ Auto add all fields to the form config. Any missing fields will be added. """ used = list(self.usedfields()) if not self.selected_layer: return for field in self.selected_layer.fields(): if field.name().lower() in used: continue self.newwidget(field) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def set_project(self, project, treenode): """ Set the project for this widget also sets the form from the tree node. :note: This method is called from the parent node when the page and widget is loaded. :param project: The current project.j :param treenode: The current tree node. Can be used to signel a update back to the tree for it to update it self. """ roam.utils.debug("FormWidget: Set Project") self.blockWidgetSignels(True) super(FormWidget, self).set_project(project, treenode) self.formlayers.setSelectLayers(self.project.selectlayers) form = self.treenode.form self.form = form self.setform(self.form) self.blockWidgetSignels(False) def blockWidgetSignels(self, blocking): for widget in self.blockWidgets: widget.blockSignals(blocking) def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): """ Get the first layer from the forms layer combo box """ index = self.formlayers.index(0, 0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): """ Find the layer with the same name in the layer combobox widget """ index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) newstyleform = settings.setdefault('newstyle', True) self.set_icon(form.icon) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.newStyleCheck.setChecked(newstyleform) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) if formtype == "auto": formtype = "Auto Generated" index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.userwidgets.setCurrentIndex(index) # self.load_widget(index, None) for i in reversed(range(self.eventsLayout.count())): child = self.eventsLayout.itemAt(i) if child.widget() and isinstance(child.widget(), EventWidget): child = self.eventsLayout.takeAt(i) child.widget().deleteLater() events = settings.get('events', []) self.load_events(events) ## This has overhead so only do it when the tab is active. if self.formtab.currentIndex() == 3: self.generate_form_preview() def load_events(self, events): for event in events: self.addEventItem(data=event) def addEventItem(self, data=None): widget = EventWidget(self.form.QGISLayer, self.widgetmodel, self.eventsWidget) widget.removeItem.connect(self.removeEventItem) widget.set_data(data) self.eventsLayout.addWidget(widget) def removeEventItem(self, widget): child = self.eventsLayout.removeWidget(widget) widget.deleteLater() def swapwidgetconfig(self, index): widgetconfig, _, _ = self.current_config_widget defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def load_widget(self, index, last): """ Update the UI with the config for the current selected widget. """ def get_id(widget_config): if widget_config: return widget_config.get('_id', None) return None widget = index.data(Qt.UserRole) lastdata = last.data(Qt.UserRole) newid = get_id(widget) lastid = get_id(lastdata) if (newid is not None and lastid is not None) and get_id(widget) == get_id(lastdata): return self.blockWidgetSignels(True) if last: roam.utils.debug("Saving last widget") self._save_widget(last) widget = index.data(Qt.UserRole) if not widget: self.blockWidgetSignels(False) return try: roam.utils.debug("Loading widget: {0}".format(get_id(widget))) except KeyError: pass widgettype = widget['widget'] if widgettype == "Section": self.propertiesStack.setCurrentIndex(1) self.sectionNameText.blockSignals(True) name = widget['name'] self.sectionNameText.setText(name) self.sectionNameText.blockSignals(False) return else: self.propertiesStack.setCurrentIndex(0) widgetconfig = WidgetConfig.from_config(widget) field = widget['field'] self._currentwidgetid = widget.setdefault('_id', str(uuid.uuid4())) required = widget.setdefault('required', False) savevalue = widget.setdefault('rememberlastvalue', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) defaultevents = widget.setdefault('default_events', ['capture']) try: data = readonly[0] except IndexError: data = 'never' index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) index = self.defaultEventsCombo.findData(defaultevents) self.defaultEventsCombo.setCurrentIndex(index) self.defaultValueExpression.setText("") self.defaultFieldCombo.setLayer(None) self.defaultFieldCombo.setLayer(None) if widgetconfig.default_type == WidgetConfig.DEFAULT_LAYER_VALUE: self.defaultTab.setCurrentIndex(1) layer = default['layer'] # TODO Handle the case of many layer fall though with defaults # Not sure how to handle this in the UI just yet if isinstance(layer, list): layer = layer[0] else: defaultfield = default['field'] expression = default['expression'] self.logger.info(defaultfield) self.defaultValueExpression.setText(expression) self.logger.debug("Layer from name: {}".format(layer)) layer = roam.api.utils.layer_by_name(layer) self.logger.debug(layer) self.defaultLayerCombo.setLayer(layer) self.defaultFieldCombo.setLayer(layer) self.defaultFieldCombo.setField(defaultfield) else: self.defaultTab.setCurrentIndex(0) self.defaultvalueText.setText(default) self.nameText.setText(name) self.requiredCheck.setChecked(required) self.savevalueCheck.setChecked(savevalue) self.hiddenCheck.setChecked(hidden) if field is not None: index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) index = self.useablewidgets.findText(widgettype) if index > -1: self.useablewidgets.setCurrentIndex(index) config = widget.get('config', {}) self.updatewidgetconfig(config) self.blockWidgetSignels(False) @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.userwidgets.currentIndex() return index.data(Qt.UserRole), index @property def current_config_widget(self): """ Return the selected widget in the widget combo. """ index = self.useablewidgets.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetconfig(self, config): configwidget, _, _ = self.current_config_widget self.setconfigwidget(configwidget, config) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_current_widget) except TypeError: pass self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_current_widget) def _save_current_widget(self, *args): _, index = self.currentuserwidget self._save_widget(index) def _save_widget(self, index): # roam.utils.debug("FormWidget: Save widget") if not self.project: return widgetdata = self._get_widget_config() try: roam.utils.debug("Saving widget {} in project {}".format(widgetdata['_id'], self.project.name)) except KeyError: pass self.widgetmodel.setData(index, widgetdata, Qt.UserRole) def _get_default_config(self): if self.defaultTab.currentIndex() == 0: return self.defaultvalueText.text() else: default = {} default['layer'] = self.defaultLayerCombo.currentLayer().name() default['field'] = self.defaultFieldCombo.currentField() default['expression'] = self.defaultValueExpression.text() default['type'] = 'layer-value' return default def _get_widget_config(self): def current_field(): row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) return field configwidget, _, widgettype = self.current_config_widget if self.propertiesStack.currentIndex() == 1: return {'name': self.sectionNameText.text(), "widget": "Section"} widget = {} widget['field'] = current_field() widget[ 'default-type'] = WidgetConfig.DEFAULT_SIMPLE if self.defaultTab.currentIndex() == 0 else WidgetConfig.DEFAULT_LAYER_VALUE widget['default'] = self._get_default_config() widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['rememberlastvalue'] = self.savevalueCheck.isChecked() widget['_id'] = self._currentwidgetid widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['default_events'] = self.defaultEventsCombo.itemData(self.defaultEventsCombo.currentIndex()) widget['hidden'] = self.hiddenCheck.isChecked() widget['config'] = configwidget.getconfig() return widget @property def selected_layer(self): index = self.formlayers.index(self.layerCombo.currentIndex(), 0) layer = index.data(Qt.UserRole) return layer def write_config(self): roam.utils.debug("Write form config") if not self.selected_layer: return self._save_current_widget() self.form.settings['layer'] = self.selected_layer.name() formtype = self.formtypeCombo.currentText() self.form.settings['type'] = "auto" if formtype == "Auto Generated" else formtype self.form.settings['label'] = self.formLabelText.text() self.form.settings['newstyle'] = self.newStyleCheck.isChecked() self.form.settings['widgets'] = list(self.widgetmodel.widgets()) events = [] for i in range(self.eventsLayout.count()): child = self.eventsLayout.itemAt(i) if child.widget() and isinstance(child.widget(), EventWidget): widget = child.widget() eventdata = widget.get_data() events.append(eventdata) self.form.settings['events'] = events
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 QQuakeDialog(QDialog, FORM_CLASS): """ The main plugin dialog """ def __init__(self, iface, parent=None): # pylint:disable=too-many-statements """Constructor.""" super().__init__(parent) self.setupUi(self) self.setObjectName('QQuakeDialog') QgsGui.enableAutoGeometryRestore(self) self.scrollArea.setStyleSheet(""" QScrollArea { background: transparent; } QScrollArea > QWidget > QWidget { background: transparent; } QScrollArea > QWidget > QScrollBar { background: 1; } """) self.scrollArea_2.setStyleSheet(self.scrollArea.styleSheet()) self.scrollArea_3.setStyleSheet(self.scrollArea.styleSheet()) self.scrollArea_4.setStyleSheet(self.scrollArea.styleSheet()) self.splitter.setStretchFactor(0, 0) self.splitter_2.setStretchFactor(0, 0) self.splitter_3.setStretchFactor(0, 0) self.splitter_4.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter_2.setStretchFactor(1, 1) self.splitter_3.setStretchFactor(1, 1) self.splitter_4.setStretchFactor(1, 1) self.fdsn_event_filter = FilterParameterWidget( iface, SERVICE_MANAGER.FDSNEVENT) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.fdsn_event_filter) self.fdsn_event_filter_container.setLayout(vl) self.earthquake_service_info_widget = ServiceInformationWidget(iface) self.fdsn_by_id_filter = FilterByIdWidget(iface, SERVICE_MANAGER.FDSNEVENT) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.fdsn_by_id_filter) self.fdsn_by_id_container.setLayout(vl) self.fdsn_by_url_widget = FetchByUrlWidget(iface, SERVICE_MANAGER.FDSNEVENT) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.fdsn_by_url_widget) self.fdsn_by_url_container.setLayout(vl) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.earthquake_service_info_widget) self.earthquake_service_info_container.setLayout(vl) self.macro_filter = FilterParameterWidget(iface, SERVICE_MANAGER.MACROSEISMIC) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_filter) self.macro_filter_container.setLayout(vl) self.macro_by_id_filter = FilterByIdWidget( iface, SERVICE_MANAGER.MACROSEISMIC) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_by_id_filter) self.macro_by_id_container.setLayout(vl) self.macro_by_url_widget = FetchByUrlWidget( iface, SERVICE_MANAGER.MACROSEISMIC) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_by_url_widget) self.macro_by_url_container.setLayout(vl) self.macro_service_info_widget = ServiceInformationWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.macro_service_info_widget) self.macro_service_info_container.setLayout(vl) self.station_filter = FilterParameterWidget( iface, SERVICE_MANAGER.FDSNSTATION) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_filter) self.station_filter_container.setLayout(vl) self.station_by_id_filter = FilterStationByIdWidget( iface, SERVICE_MANAGER.FDSNSTATION) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_by_id_filter) self.station_by_id_container.setLayout(vl) self.station_service_info_widget = ServiceInformationWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_service_info_widget) self.station_service_info_container.setLayout(vl) self.station_by_url_widget = FetchByUrlWidget( iface, SERVICE_MANAGER.FDSNSTATION) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.station_by_url_widget) self.station_by_url_container.setLayout(vl) self.ogc_service_widget = OgcServiceWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.ogc_service_widget) self.ogc_widget_container.setLayout(vl) self.ogc_service_info_widget = ServiceInformationWidget(iface) vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) vl.addWidget(self.ogc_service_info_widget) self.ogc_service_info_container.setLayout(vl) self.message_bar = QgsMessageBar() self.message_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.verticalLayout.insertWidget(0, self.message_bar) self.fdsn_event_url_text_browser.viewport().setAutoFillBackground( False) self.fdsn_macro_url_text_browser.viewport().setAutoFillBackground( False) self.fdsn_station_url_text_browser.viewport().setAutoFillBackground( False) self.button_box.button(QDialogButtonBox.Ok).setText( self.tr('Fetch Data')) self.button_box.rejected.connect(self._save_settings) self.iface = iface # OGC self.ogc_combo.addItem(self.tr('Web Map Services (WMS)'), SERVICE_MANAGER.WMS) self.ogc_combo.addItem(self.tr('Web Feature Services (WFS)'), SERVICE_MANAGER.WFS) self.ogc_combo.currentIndexChanged.connect(self.refreshOgcWidgets) self.ogc_list_model = QStandardItemModel(self.ogc_list) self.ogc_list.setModel(self.ogc_list_model) self.ogc_list.selectionModel().selectionChanged.connect( self._ogc_service_changed) self._refresh_services() SERVICE_MANAGER.refreshed.connect(self._refresh_services) # connect to refreshing function to refresh the UI depending on the WS self._refresh_fdsnevent_widgets() self.refreshFdsnMacroseismicWidgets() self.refreshFdsnStationWidgets() # change the UI parameter according to the web service chosen self.fdsn_event_list.currentRowChanged.connect( self._refresh_fdsnevent_widgets) self.fdsn_macro_list.currentRowChanged.connect( self.refreshFdsnMacroseismicWidgets) self.fdsn_station_list.currentRowChanged.connect( self.refreshFdsnStationWidgets) self.fdsn_event_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_by_id_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_by_url_widget.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_event_list.currentRowChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.macro_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.macro_by_id_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.macro_by_url_widget.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.fdsn_macro_list.currentRowChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.station_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.station_by_id_filter.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.fdsn_station_list.currentRowChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.station_by_url_widget.changed.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) self.button_box.accepted.connect(self._getEventList) self.service_tab_widget.currentChanged.connect( lambda: self._refresh_url(None)) self.fetcher = None QgsGui.enableAutoGeometryRestore(self) self.fdsn_tab_widget.currentChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNEVENT)) self.macro_tab_widget.currentChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.MACROSEISMIC)) self.fdsnstation_tab_widget.currentChanged.connect( lambda: self._refresh_url(SERVICE_MANAGER.FDSNSTATION)) for b in [ self.button_fdsn_new_service, self.button_macro_new_service, self.button_station_new_service, self.button_ogc_new_service ]: self._build_add_service_menu(b) for b in [ self.button_fdsn_edit_service, self.button_macro_edit_service, self.button_station_edit_service, self.button_ogc_edit_service ]: b.clicked.connect(self._edit_service) for b in [ self.button_fdsn_rename_service, self.button_macro_rename_service, self.button_station_rename_service, self.button_ogc_rename_service ]: b.clicked.connect(self._rename_service) for b in [ self.button_fdsn_remove_service, self.button_macro_remove_service, self.button_station_remove_service, self.button_ogc_remove_service ]: b.clicked.connect(self._remove_service) for b in [ self.button_fdsn_export_service, self.button_macro_export_service, self.button_station_export_service, self.button_ogc_export_service ]: b.clicked.connect(self._export_service) self._restore_settings() self._refresh_url(SERVICE_MANAGER.FDSNEVENT) self._refresh_url(SERVICE_MANAGER.MACROSEISMIC) self._refresh_url(SERVICE_MANAGER.FDSNSTATION) def closeEvent(self, e): # pylint: disable=missing-function-docstring self._save_settings() super().closeEvent(e) def _build_add_service_menu(self, widget): """ Builds the add service menu for a specific widget """ menu = QMenu() save_action = QAction(self.tr('Save Current Configuration As…'), parent=menu) save_action.setObjectName('save_action') menu.addAction(save_action) save_action.triggered.connect(self._save_configuration) import_action = QAction(self.tr('Import from File…'), parent=menu) menu.addAction(import_action) import_action.triggered.connect(self._import_configuration) create_new_action = QAction(self.tr('Create New Service…'), parent=menu) menu.addAction(create_new_action) create_new_action.triggered.connect(self._create_configuration) menu.aboutToShow.connect(lambda: self._menu_about_to_show(menu)) widget.setMenu(menu) def _menu_about_to_show(self, menu): """ Triggered when the Add Service menu is about to show """ save_current_action = menu.findChild(QAction, 'save_action') service_type = self.get_current_service_type() filter_widget = self.get_service_filter_widget(service_type) save_current_action.setEnabled( hasattr(filter_widget, 'to_service_definition')) def _refresh_services(self): """ Refreshes the list of available services """ # fill the FDSN listWidget with the dictionary keys self.fdsn_event_list.clear() self.fdsn_event_list.addItems( SERVICE_MANAGER.available_services(SERVICE_MANAGER.FDSNEVENT)) self.fdsn_event_list.setCurrentRow(0) # fill the FDSN listWidget with the dictionary keys self.fdsn_macro_list.clear() self.fdsn_macro_list.addItems( SERVICE_MANAGER.available_services(SERVICE_MANAGER.MACROSEISMIC)) self.fdsn_macro_list.setCurrentRow(0) # fill the FDSN listWidget with the dictionary keys self.fdsn_station_list.clear() self.fdsn_station_list.addItems( SERVICE_MANAGER.available_services(SERVICE_MANAGER.FDSNSTATION)) self.fdsn_station_list.setCurrentRow(0) self.refreshOgcWidgets() def _save_configuration(self): """ Triggers saving the current service configuration """ service_type = self.get_current_service_type() name, ok = QInputDialog.getText( self, self.tr('Save Service Configuration'), self.tr('Save the current service configuration as')) if not name or not ok: return filter_widget = self.get_service_filter_widget(service_type) SERVICE_MANAGER.save_service(service_type, name, filter_widget.to_service_definition()) self.set_current_service(service_type, name) def _save_settings(self): """ Saves all settings currently defined in the dialog """ s = QgsSettings() if self.service_tab_widget.currentIndex( ) != self.service_tab_widget.count() - 1: s.setValue('/plugins/qquake/last_tab', self.service_tab_widget.currentIndex()) s.setValue('/plugins/qquake/fdsn_event_last_event_service', self.fdsn_event_list.currentItem().text()) s.setValue('/plugins/qquake/macro_last_event_service', self.fdsn_macro_list.currentItem().text()) s.setValue('/plugins/qquake/fdsnevent_last_tab', self.fdsn_tab_widget.currentIndex()) s.setValue('/plugins/qquake/macro_last_tab', self.macro_tab_widget.currentIndex()) s.setValue('/plugins/qquake/station_last_tab', self.fdsnstation_tab_widget.currentIndex()) self.fdsn_event_filter.save_settings('fdsn_event') self.fdsn_by_id_filter.save_settings('fdsn_event') self.fdsn_by_url_widget.save_settings('fdsn_event') self.macro_filter.save_settings('macro') self.macro_by_id_filter.save_settings('macro') self.macro_by_url_widget.save_settings('macro') self.station_filter.save_settings('stations') self.station_by_id_filter.save_settings('stations') self.station_by_url_widget.save_settings('stations') def _restore_settings(self): """ Restores dialog settings """ s = QgsSettings() last_tab = s.value('/plugins/qquake/last_tab') if last_tab is not None: self.service_tab_widget.setCurrentIndex(int(last_tab)) last_service = s.value('/plugins/qquake/fdsn_event_last_event_service') if last_service is not None: try: self.fdsn_event_list.setCurrentItem( self.fdsn_event_list.findItems(last_service, Qt.MatchContains)[0]) except IndexError: pass last_service = s.value('/plugins/qquake/macro_last_event_service') if last_service is not None: self.fdsn_macro_list.setCurrentItem( self.fdsn_macro_list.findItems(last_service, Qt.MatchContains)[0]) self.fdsn_event_filter.restore_settings('fdsn_event') self.fdsn_by_id_filter.restore_settings('fdsn_event') self.fdsn_by_url_widget.restore_settings('fdsn_event') self.macro_filter.restore_settings('macro') self.macro_by_id_filter.restore_settings('macro') self.macro_by_url_widget.restore_settings('fdsn_event') self.station_filter.restore_settings('stations') self.station_by_id_filter.restore_settings('stations') self.station_by_url_widget.restore_settings('stations') self.fdsn_tab_widget.setCurrentIndex( s.value('/plugins/qquake/fdsnevent_last_tab', 0, int)) self.macro_tab_widget.setCurrentIndex( s.value('/plugins/qquake/macro_last_tab', 0, int)) self.fdsnstation_tab_widget.setCurrentIndex( s.value('/plugins/qquake/station_last_tab', 0, int)) def get_current_service_id(self, service_type: str) -> Optional[str]: """ Returns the current selected service id """ if service_type == SERVICE_MANAGER.FDSNEVENT: service_id = self.fdsn_event_list.currentItem().text( ) if self.fdsn_event_list.currentItem() else None elif service_type == SERVICE_MANAGER.MACROSEISMIC: service_id = self.fdsn_macro_list.currentItem().text( ) if self.fdsn_macro_list.currentItem() else None elif service_type == SERVICE_MANAGER.FDSNSTATION: service_id = self.fdsn_station_list.currentItem().text( ) if self.fdsn_station_list.currentItem() else None elif service_type in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): service_id = self.ogc_list.selectionModel().selectedIndexes( )[0].data() if self.ogc_list.selectionModel().selectedIndexes( ) else None else: service_id = None return service_id def get_current_service_type(self) -> Optional[str]: """ Returns the current service type """ if self.service_tab_widget.currentIndex() == 0: service_type = SERVICE_MANAGER.FDSNEVENT elif self.service_tab_widget.currentIndex() == 1: service_type = SERVICE_MANAGER.MACROSEISMIC elif self.service_tab_widget.currentIndex() == 2: service_type = SERVICE_MANAGER.FDSNSTATION elif self.service_tab_widget.currentIndex() == 3: return self.ogc_combo.currentData() else: service_type = None return service_type def get_service_filter_widget( self, # pylint:disable=too-many-branches service_type: str ) -> Optional[ Union[FilterParameterWidget, FilterByIdWidget, FetchByUrlWidget, FilterStationByIdWidget, OgcServiceWidget]]: """ Returns the service filter widget for a specific service type """ widget = None if service_type == SERVICE_MANAGER.FDSNEVENT: if self.fdsn_tab_widget.currentIndex() in (0, 3): widget = self.fdsn_event_filter elif self.fdsn_tab_widget.currentIndex() == 1: widget = self.fdsn_by_id_filter elif self.fdsn_tab_widget.currentIndex() == 2: widget = self.fdsn_by_url_widget elif service_type == SERVICE_MANAGER.MACROSEISMIC: if self.macro_tab_widget.currentIndex() in (0, 3): widget = self.macro_filter elif self.macro_tab_widget.currentIndex() == 1: widget = self.macro_by_id_filter elif self.macro_tab_widget.currentIndex() == 2: widget = self.macro_by_url_widget elif service_type == SERVICE_MANAGER.FDSNSTATION: if self.fdsnstation_tab_widget.currentIndex() in (0, 3): widget = self.station_filter elif self.fdsnstation_tab_widget.currentIndex() == 1: widget = self.station_by_id_filter elif self.fdsnstation_tab_widget.currentIndex() == 2: widget = self.station_by_url_widget elif service_type in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): widget = self.ogc_service_widget return widget def get_fetcher(self, service_type: Optional[str] = None): """ Returns a quake fetcher corresponding to the current dialog settings """ if service_type is None: service_type = self.get_current_service_type() service = self.get_current_service_id(service_type) if not service: return None filter_widget = self.get_service_filter_widget(service_type) service_config = SERVICE_MANAGER.service_details(service_type, service) if isinstance(filter_widget, FilterParameterWidget): fetcher = Fetcher( service_type=service_type, event_service=service, event_start_date=filter_widget.start_date(), event_end_date=filter_widget.end_date(), event_min_magnitude=filter_widget.min_magnitude(), event_max_magnitude=filter_widget.max_magnitude(), limit_extent_rect=filter_widget.extent_rect(), min_latitude=filter_widget.min_latitude(), max_latitude=filter_widget.max_latitude(), min_longitude=filter_widget.min_longitude(), max_longitude=filter_widget.max_longitude(), limit_extent_circle=filter_widget.limit_extent_circle(), circle_latitude=filter_widget.circle_latitude(), circle_longitude=filter_widget.circle_longitude(), circle_min_radius=filter_widget.circle_min_radius(), circle_max_radius=filter_widget.circle_max_radius(), circle_radius_unit=filter_widget.circle_radius_unit(), earthquake_number_mdps_greater=filter_widget. earthquake_number_mdps_greater(), earthquake_max_intensity_greater=filter_widget. earthquake_max_intensity_greater(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget.convert_negative_depths( ), depth_unit=filter_widget.depth_unit(), event_type=filter_widget.event_type(), updated_after=filter_widget.updated_after()) elif isinstance(filter_widget, FilterByIdWidget): if not service_config['settings'].get('queryeventid'): fetcher = None else: fetcher = Fetcher( service_type=service_type, event_service=service, event_ids=filter_widget.ids(), contributor_id=filter_widget.contributor_id(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget. convert_negative_depths(), depth_unit=filter_widget.depth_unit()) elif isinstance(filter_widget, FetchByUrlWidget): fetcher = Fetcher(service_type=service_type, event_service=service, url=filter_widget.url(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget. convert_negative_depths(), depth_unit=filter_widget.depth_unit()) elif isinstance(filter_widget, FilterStationByIdWidget): fetcher = Fetcher(service_type=service_type, event_service=service, network_codes=filter_widget.network_codes(), station_codes=filter_widget.station_codes(), locations=filter_widget.locations(), output_fields=filter_widget.output_fields, output_type=filter_widget.output_type(), convert_negative_depths=filter_widget. convert_negative_depths(), depth_unit=filter_widget.depth_unit()) return fetcher def _refresh_url(self, service_type: Optional[str] = None): """ Updates the service URL """ if not service_type: service_type = self.get_current_service_type() if service_type is None: self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) return if service_type not in (SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC, SERVICE_MANAGER.FDSNSTATION): self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) return fetcher = self.get_fetcher(service_type) if not fetcher: self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) return self._valid_changed() if service_type == SERVICE_MANAGER.FDSNEVENT: self.fdsn_event_url_text_browser.setText( '<a href="{0}">{0}</a>'.format(fetcher.generate_url())) elif service_type == SERVICE_MANAGER.MACROSEISMIC: self.fdsn_macro_url_text_browser.setText( '<a href="{0}">{0}</a>'.format(fetcher.generate_url())) elif service_type == SERVICE_MANAGER.FDSNSTATION: self.fdsn_station_url_text_browser.setText( '<a href="{0}">{0}</a>'.format(fetcher.generate_url())) def _valid_changed(self): """ Called when dialog entry validation should occur """ service_type = self.get_current_service_type() if service_type not in (SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC, SERVICE_MANAGER.FDSNSTATION): self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) return filter_widget = self.get_service_filter_widget(service_type) self.button_box.button(QDialogButtonBox.Ok).setEnabled( filter_widget.is_valid()) def _update_service_widgets( self, # pylint: disable=too-many-locals,too-many-branches service_type, service_id, filter_widget, filter_by_id_widget, fetch_by_url_widget, info_widget, remove_service_button, edit_service_button, rename_service_button, tab_widget): """ Updates all widgets to reflect the current service details """ service_config = SERVICE_MANAGER.service_details( service_type, service_id) date_start = QDateTime.fromString(service_config['datestart'], Qt.ISODate) default_date_start = QDateTime.fromString( service_config['default']['datestart'], Qt.ISODate) if service_config['default'].get('datestart') else None # if the dateend is not set in the config.json set the date to NOW date_end = QDateTime.fromString( service_config['dateend'], Qt.ISODate) if 'dateend' in service_config and service_config[ 'dateend'] else None default_date_end = QDateTime.fromString( service_config['default']['dateend'], Qt.ISODate) if service_config['default'].get('dateend') else None filter_widget.set_date_range_limits(date_start, date_end) filter_widget.set_current_date_range(default_date_start, default_date_end) if service_config['default'].get('boundingboxpredefined'): filter_widget.set_predefined_bounding_box( service_config['default'].get('boundingboxpredefined')) if service_config['default'].get('minimumlatitude'): filter_widget.set_min_latitude( service_config['default'].get('minimumlatitude')) if service_config['default'].get('maximumlatitude'): filter_widget.set_max_latitude( service_config['default'].get('maximumlatitude')) if service_config['default'].get('minimumlongitude'): filter_widget.set_min_longitude( service_config['default'].get('minimumlongitude')) if service_config['default'].get('maximumlongitude'): filter_widget.set_max_longitude( service_config['default'].get('maximumlongitude')) if service_config['default'].get('circlelatitude'): filter_widget.set_circle_latitude( service_config['default'].get('circlelatitude')) if service_config['default'].get('circlelongitude'): filter_widget.set_circle_longitude( service_config['default'].get('circlelongitude')) if service_config['default'].get('minimumcircleradius'): filter_widget.set_min_circle_radius( service_config['default'].get('minimumcircleradius')) if service_config['default'].get('maximumcircleradius'): filter_widget.set_max_circle_radius( service_config['default'].get('maximumcircleradius')) if service_config['default'].get('minimummagnitude'): filter_widget.set_min_magnitude( service_config['default'].get('minimummagnitude')) if service_config['default'].get('maximummagnitude'): filter_widget.set_max_magnitude( service_config['default'].get('maximummagnitude')) if service_config['default'].get('macromaxintensitygreater'): filter_widget.set_max_intensity_greater( service_config['default'].get('macromaxintensitygreater')) if service_config['default'].get('macromdpsgreaterthan'): filter_widget.set_mdps_greater_than( service_config['default'].get('macromdpsgreaterthan')) if service_config['default'].get('eventtype'): filter_widget.set_event_type( service_config['default'].get('eventtype')) updated_after = QDateTime.fromString( service_config['default']['updatedafter'], Qt.ISODate ) if service_config['default'].get('updatedafter') else None if updated_after: filter_widget.set_updated_after(updated_after) filter_widget.set_extent_limit( service_config.get('boundingbox', [-180, -90, 180, 90])) if service_type in [ SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC ]: tab_widget.widget(1).setEnabled(service_config['settings'].get( 'queryeventid', False)) info_widget.set_service(service_type=service_type, service_id=service_id) filter_widget.set_service_id(service_id) filter_by_id_widget.set_service_id(service_id) if fetch_by_url_widget is not None: fetch_by_url_widget.set_service_id(service_id) remove_service_button.setEnabled(not service_config['read_only']) edit_service_button.setEnabled(not service_config['read_only']) rename_service_button.setEnabled(not service_config['read_only']) def _refresh_fdsnevent_widgets(self): """ Refreshing the FDSN-Event UI depending on the WS chosen """ if not self.fdsn_event_list.currentItem(): return service_id = self.fdsn_event_list.currentItem().text() self._update_service_widgets( service_type=SERVICE_MANAGER.FDSNEVENT, service_id=service_id, filter_widget=self.fdsn_event_filter, filter_by_id_widget=self.fdsn_by_id_filter, fetch_by_url_widget=self.fdsn_by_url_widget, info_widget=self.earthquake_service_info_widget, remove_service_button=self.button_fdsn_remove_service, edit_service_button=self.button_fdsn_edit_service, rename_service_button=self.button_fdsn_rename_service, tab_widget=self.fdsn_tab_widget) def refreshFdsnMacroseismicWidgets(self): """ Refreshing the FDSN-Macroseismic UI depending on the WS chosen """ if not self.fdsn_macro_list.currentItem(): return service_id = self.fdsn_macro_list.currentItem().text() self._update_service_widgets( service_type=SERVICE_MANAGER.MACROSEISMIC, service_id=service_id, filter_widget=self.macro_filter, filter_by_id_widget=self.macro_by_id_filter, fetch_by_url_widget=self.macro_by_url_widget, info_widget=self.macro_service_info_widget, remove_service_button=self.button_macro_remove_service, edit_service_button=self.button_macro_edit_service, rename_service_button=self.button_macro_rename_service, tab_widget=self.macro_tab_widget) def refreshFdsnStationWidgets(self): """ Refreshing the FDSN-Macroseismic UI depending on the WS chosen """ if not self.fdsn_station_list.currentItem(): return service_id = self.fdsn_station_list.currentItem().text() self._update_service_widgets( service_type=SERVICE_MANAGER.FDSNSTATION, service_id=service_id, filter_by_id_widget=self.station_by_id_filter, fetch_by_url_widget=self.station_by_url_widget, filter_widget=self.station_filter, info_widget=self.station_service_info_widget, remove_service_button=self.button_station_remove_service, edit_service_button=self.button_station_edit_service, rename_service_button=self.button_station_rename_service, tab_widget=self.fdsnstation_tab_widget) def refreshOgcWidgets(self): """ read the ogc_combo and fill it with the services """ self.ogc_list_model.clear() ogc_selection = self.ogc_combo.currentData() services = SERVICE_MANAGER.available_services(ogc_selection) group_items = {} for service in services: service_config = SERVICE_MANAGER.service_details( ogc_selection, service) group = service_config.get('group') if not group or group in group_items: continue group_item = QStandardItem(group) group_item.setFlags(Qt.ItemIsEnabled) self.ogc_list_model.appendRow([group_item]) group_items[group] = group_item first_item = None for service in services: item = QStandardItem(service) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) item.setData(service, role=Qt.UserRole) if not first_item: first_item = item service_config = SERVICE_MANAGER.service_details( ogc_selection, service) group = service_config.get('group') if group: parent = group_items[group] parent.appendRow([item]) else: self.ogc_list_model.appendRow([item]) self.ogc_list.expandAll() first_item_index = self.ogc_list_model.indexFromItem(first_item) self.ogc_list.selectionModel().select( first_item_index, QItemSelectionModel.ClearAndSelect) service_config = SERVICE_MANAGER.service_details( ogc_selection, self.get_current_service_id(ogc_selection)) self.button_ogc_edit_service.setEnabled( not service_config['read_only']) self.button_ogc_rename_service.setEnabled( not service_config['read_only']) self.button_ogc_remove_service.setEnabled( not service_config['read_only']) def _ogc_service_changed(self, _, __): """ Triggered when the current OGC service changes """ if not self.ogc_list.selectionModel().selectedIndexes(): return current_service = self.ogc_list.selectionModel().selectedIndexes( )[0].data(Qt.UserRole) if not current_service: return self.ogc_service_widget.set_service( service_type=self.ogc_combo.currentData(), service_id=current_service) self.ogc_service_info_widget.set_service( service_type=self.ogc_combo.currentData(), service_id=current_service) service_config = SERVICE_MANAGER.service_details( self.ogc_combo.currentData(), current_service) self.button_ogc_edit_service.setEnabled( not service_config['read_only']) self.button_ogc_rename_service.setEnabled( not service_config['read_only']) self.button_ogc_remove_service.setEnabled( not service_config['read_only']) def _remove_service(self): """ Removes the current service """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) if QMessageBox.question( self, self.tr('Remove Service'), self.tr('Are you sure you want to remove "{}"?'.format( service_id))) != QMessageBox.Yes: return SERVICE_MANAGER.remove_service(service_type, service_id) def _edit_service(self): """ Edits the current service """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) config_dialog = ServiceConfigurationDialog(self.iface, service_type, service_id, self) if config_dialog.exec_(): self.set_current_service(service_type, service_id) def _rename_service(self): """ Renames the current service """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) dlg = QgsNewNameDialog( service_id, service_id, [], existing=SERVICE_MANAGER.available_services(service_type)) dlg.setHintString(self.tr('Rename service configuration to')) dlg.setWindowTitle(self.tr('Rename Service Configuration')) dlg.setOverwriteEnabled(False) dlg.setConflictingNameWarning( self.tr('A configuration with this name already exists')) if not dlg.exec_(): return new_name = dlg.name() SERVICE_MANAGER.rename_service(service_type, service_id, new_name) self.set_current_service(service_type, new_name) def set_current_service(self, service_type: str, service_id: str): """ Sets the current service """ if service_type == SERVICE_MANAGER.FDSNEVENT: self.fdsn_event_list.setCurrentItem( self.fdsn_event_list.findItems(service_id, Qt.MatchContains)[0]) elif service_type == SERVICE_MANAGER.MACROSEISMIC: self.fdsn_macro_list.setCurrentItem( self.fdsn_macro_list.findItems(service_id, Qt.MatchContains)[0]) elif service_type == SERVICE_MANAGER.FDSNSTATION: self.fdsn_station_list.setCurrentItem( self.fdsn_station_list.findItems(service_id, Qt.MatchContains)[0]) elif service_type in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): self.ogc_combo.setCurrentIndex( self.ogc_combo.findData(service_type)) indexes = self.ogc_list_model.match( self.ogc_list_model.index(0, 0), Qt.UserRole, service_id, flags=Qt.MatchExactly | Qt.MatchRecursive) if len(indexes) > 0: self.ogc_list.selectionModel().select( indexes[0], QItemSelectionModel.ClearAndSelect) def _create_configuration(self): """ Creates a new service configuration """ service_type = self.get_current_service_type() dlg = QgsNewNameDialog( '', '', [], existing=SERVICE_MANAGER.available_services(service_type)) dlg.setHintString(self.tr('Create a new service configuration named')) dlg.setWindowTitle(self.tr('New Service Configuration')) dlg.setOverwriteEnabled(False) dlg.setConflictingNameWarning( self.tr('A configuration with this name already exists')) if not dlg.exec_(): return name = dlg.name() config_dialog = ServiceConfigurationDialog(self.iface, service_type, name, self) if config_dialog.exec_(): self.set_current_service(service_type, name) def _export_service(self): """ Triggers exporting a service configuration """ service_type = self.get_current_service_type() service_id = self.get_current_service_id(service_type) file, _ = QFileDialog.getSaveFileName( self, self.tr('Export Service'), QDir.homePath() + '/{}.json'.format(service_id), 'JSON Files (*.json)') if not file: return file = QgsFileUtils.ensureFileNameHasExtension(file, ['json']) if SERVICE_MANAGER.export_service(service_type, service_id, file): self.message_bar.pushMessage(self.tr("Service exported"), Qgis.Success, 5) else: self.message_bar.pushMessage( self.tr("An error occurred while exporting service"), Qgis.Critical, 5) def _import_configuration(self): """ Triggers importing a configuration """ file, _ = QFileDialog.getOpenFileName(self, self.tr('Import Service'), QDir.homePath(), 'JSON Files (*.json)') if not file: return res, err = SERVICE_MANAGER.import_service(file) if res: self.message_bar.pushMessage(self.tr("Service imported"), Qgis.Success, 5) else: self.message_bar.pushMessage(err, Qgis.Critical, 5) def _getEventList(self): """ read the event URL and convert the response in a list """ if self.get_current_service_type() in (SERVICE_MANAGER.WMS, SERVICE_MANAGER.WFS): self.ogc_service_widget.add_selected_layers() return if self.fetcher: # TODO - cancel current request return self.fetcher = self.get_fetcher() def on_started(): self.progressBar.setValue(0) self.progressBar.setRange(0, 0) def on_progress(progress: float): self.progressBar.setRange(0, 100) self.progressBar.setValue(progress) self.fetcher.started.connect(on_started) self.fetcher.progress.connect(on_progress) self.fetcher.finished.connect(self._fetcher_finished) self.fetcher.message.connect(self._fetcher_message) self.button_box.button(QDialogButtonBox.Ok).setText( self.tr('Fetching')) self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.fetcher.fetch_data() def _fetcher_message(self, message, level): """ Handles message feedback from a fetcher """ self.message_bar.clearWidgets() self.message_bar.pushMessage(message, level, 0) def _fetcher_finished(self, res): # pylint: disable=too-many-branches """ Triggered when a fetcher is finished """ self.progressBar.setRange(0, 100) self.progressBar.reset() self.button_box.button(QDialogButtonBox.Ok).setText( self.tr('Fetch Data')) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) if not res: self.fetcher.deleteLater() self.fetcher = None return found_results = False layers = [] if self.fetcher.service_type in (SERVICE_MANAGER.FDSNEVENT, SERVICE_MANAGER.MACROSEISMIC): layer = self.fetcher.create_event_layer() if layer: layers.append(layer) if self.fetcher.service_type == SERVICE_MANAGER.MACROSEISMIC: layer = self.fetcher.create_mdp_layer() if layer: layers.append(layer) if layers: events_count = layers[0].featureCount() found_results = bool(events_count) service_limit = self.fetcher.service_config['settings'].get( 'querylimitmaxentries', None) self.message_bar.clearWidgets() if service_limit is not None and events_count >= service_limit: self.message_bar.pushMessage( self.tr("Query exceeded the service's result limit"), Qgis.Critical, 0) elif events_count > 500: self.message_bar.pushMessage( self.tr( "Query returned a large number of results ({})". format(events_count)), Qgis.Warning, 0) elif events_count == 0: self.message_bar.pushMessage( self. tr("Query returned no results - possibly parameters are invalid for this service" ), Qgis.Critical, 0) else: self.message_bar.pushMessage( self.tr("Query returned {} records").format( events_count), Qgis.Success, 0) elif self.fetcher.service_type == SERVICE_MANAGER.FDSNSTATION: layers.append(self.fetcher.create_stations_layer()) stations_count = layers[0].featureCount() found_results = bool(stations_count) if stations_count == 0: self.message_bar.pushMessage( self. tr("Query returned no results - possibly parameters are invalid for this service" ), Qgis.Critical, 0) else: self.message_bar.pushMessage( self.tr("Query returned {} stations").format( stations_count), Qgis.Info, 0) else: assert False self.fetcher.deleteLater() self.fetcher = None if found_results: QgsProject.instance().addMapLayers(layers)
class QVDistrictesBarris(QObject): __distBarrisCSV = r'Dades\DIST_BARRIS.csv' __zones = r'Dades\Zones.gpkg' def __init__(self): super().__init__() self.labels = [] self.registre = {} # Model self.model = QStandardItemModel() self.llegirZonesGPKG() #self.llegirDistrictesBarrisCSV() # View self.view = QTreeView() self.view.setContextMenuPolicy(Qt.ActionsContextMenu) self.actExpand = QAction("Expandeix/Contreu Tot", self) self.actExpand.setStatusTip("Expand") self.actExpand.triggered.connect(self.expand_all) self.view.addAction(self.actExpand) self.view.setModel(self.model) self.iniView() def expand_all(self): """Expandir o contraer todo el arbol, dependiendo de si detecta que esta extendido o contraido """ if self.view.isExpanded(self.model.index(0, 0, QModelIndex())): self.view.collapseAll() else: self.view.expandAll() def iniView(self): self.view.setHeaderHidden(True) for i in range(self.model.columnCount()): if i == 0: self.view.setColumnHidden(i, False) self.view.resizeColumnToContents(i) self.view.resizeColumnToContents(i) else: self.view.setColumnHidden(i, True) self.view.setEditTriggers(QTreeView.NoEditTriggers) self.expand_all() def llegirZonesGPKG(self): try: QvFuncions.setReadOnlyFile(self.__zones) pathDistrictes = self.__zones + '|layername=districtes' layerDistrictes = QgsVectorLayer(pathDistrictes, 'ogr') pathBarris = self.__zones + '|layername=barris' layerBarris = QgsVectorLayer(pathBarris, 'ogr') rowsDistrictes = layerDistrictes.getFeatures() llistaDistrictes = [] for rowD in rowsDistrictes: #print(rowD.attributes()) #zona = "" num_districte = rowD.attributes()[1] nom_districte = rowD.attributes()[2] num_barri = "" nom_barri = "" geometria = rowD.geometry().boundingBox() x_min = str(geometria.xMinimum()) y_min = str(geometria.yMinimum()) x_max = str(geometria.xMaximum()) y_max = str(geometria.yMaximum()) item = [ num_districte, nom_districte, num_barri, nom_barri, x_min, y_min, x_max, y_max ] llistaDistrictes.append(item) def ordenaPerNumDistricte(elem): return elem[0] llistaDistrictes.sort(key=ordenaPerNumDistricte) #print(llistaDistrictes) rowsBarris = layerBarris.getFeatures() llistaBarris = [] for rowB in rowsBarris: #print(rowB.attributes()) #zona = "" num_districte = rowB.attributes()[3] nom_districte = llistaDistrictes[int(num_districte) - 1][1] num_barri = rowB.attributes()[1] nom_barri = rowB.attributes()[2] geometria = rowB.geometry().boundingBox() x_min = str(geometria.xMinimum()) y_min = str(geometria.yMinimum()) x_max = str(geometria.xMaximum()) y_max = str(geometria.yMaximum()) item = [ num_districte, nom_districte, num_barri, nom_barri, x_min, y_min, x_max, y_max ] llistaBarris.append(item) def ordenaPerNumBarri(elem): return elem[2] llistaBarris.sort(key=ordenaPerNumBarri) #print(llistaBarris) self.labels = [ "ZONA", "DISTRICTE", "NOM_DISTRICTE", "BARRI", "NOM_BARRI", "X_MIN", "Y_MIN", "X_MAX", "Y_MAX" ] root = self.model.invisibleRootItem() self.model.setColumnCount(len(self.labels)) self.model.setHorizontalHeaderLabels(self.labels) #Afegir Barcelona com a arrel de l'arbre bcn_dades = [ "00", "Barcelona", "00", "Barcelona", "419710.0553820258", "4573818.80776309", "436533.35", "4591775.02" ] bcn = [QStandardItem("Barcelona")] for item in bcn_dades: bcn.append(QStandardItem(item)) root.appendRow(bcn) ultimaDistr = -1 itDist = 0 for b in llistaBarris: if ultimaDistr != int(b[0]): #Afegir següent districte dist = [QStandardItem(llistaDistrictes[itDist][1])] for i in range(0, len(llistaDistrictes[itDist])): dist.append(QStandardItem(llistaDistrictes[itDist][i])) bcn[0].appendRow(dist) itDist = itDist + 1 #Afegir següent Barri barri = [QStandardItem(b[3])] for item in b: barri.append(QStandardItem(item)) dist[0].appendRow(barri) ultimaDistr = int(b[0]) return True except: print("Error en construcció de l'arbre de zones") return False # def llegirDistrictesBarrisCSV(self): # try: # first = True # with open(self.__distBarrisCSV, newline='') as csvFile: # reader = csv.DictReader(csvFile, delimiter=';') # root = self.model.invisibleRootItem() # for row in reader: # if first: # Primer registro # self.labels = ['ZONA'] # for item in row: # self.labels.append(item) # self.model.setColumnCount(len(self.labels)) # self.model.setHorizontalHeaderLabels(self.labels) # first = False # if row['BARRI'] == '': # Registro de distrito # dist = [QStandardItem(row['NOM_DISTRICTE'])] # for item in row.values(): # dist.append(QStandardItem(item)) # root.appendRow(dist) # else: # Registro de barrio # barri = [QStandardItem(row['NOM_BARRI'])] # for item in row.values(): # barri.append(QStandardItem(item)) # dist[0].appendRow(barri) # return True # except: # print('QDistrictesBarris.llegirDistrictesBarrisCSV(): ', sys.exc_info()[0], sys.exc_info()[1]) # return False def llegirRegistre(self): try: click = self.view.currentIndex() #Controlarem si s'ha canviat d'índex o no if hasattr(self, 'ultimIndex') and self.ultimIndex == click: return self.registre self.ultimIndex = click self.registre = {} for i in range(self.model.columnCount()): index = click.sibling(click.row(), i) item = self.model.itemFromIndex(index) self.registre[self.labels[i]] = item.text() self.registre['RANG'] = QgsRectangle(float(self.registre['X_MIN']), \ float(self.registre['Y_MIN']), \ float(self.registre['X_MAX']), \ float(self.registre['Y_MAX'])) except: print('QDistrictesBarris.llegirRegistre(): ', sys.exc_info()[0], sys.exc_info()[1]) finally: return self.registre def llegirRang(self): return self.llegirRegistre()['RANG'] def esDistricte(self): return self.llegirRegistre()['BARRI'] == '' def esBarri(self): return not self.esDistricte() def llegirNom(self): if self.esDistricte(): distr = self.llegirRegistre()["NOM_DISTRICTE"] distr_d = distr + "_d" return distr_d else: barri = self.llegirRegistre()["NOM_BARRI"] if barri == "Barcelona": return barri else: barri_b = barri + "_b" return barri_b def llegirID(self): if self.esDistricte(): return self.llegirRegistre()['DISTRICTE'] return self.llegirRegistre()['BARRI']
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 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 DoProfile(QWidget): def __init__(self, iface, dockwidget1 , tool1 , plugin, parent = None): QWidget.__init__(self, parent) self.profiles = None #dictionary where is saved the plotting data {"l":[l],"z":[z], "layer":layer1, "curve":curve1} self.xAxisSteps = None self.xAxisStepType = "numeric" self.iface = iface self.tool = tool1 self.dockwidget = dockwidget1 self.pointstoDraw = None self.plugin = plugin #init scale widgets self.dockwidget.sbMaxVal.setValue(0) self.dockwidget.sbMinVal.setValue(0) self.dockwidget.sbMaxVal.setEnabled(False) self.dockwidget.sbMinVal.setEnabled(False) self.dockwidget.sbMinVal.valueChanged.connect(self.reScalePlot) self.dockwidget.sbMaxVal.valueChanged.connect(self.reScalePlot) #**************************** function part ************************************************* # remove layers which were removed from QGIS def removeClosedLayers(self, model1): qgisLayerNames = [] for i in range(0, self.iface.mapCanvas().layerCount()): qgisLayerNames.append(self.iface.mapCanvas().layer(i).name()) for i in range(0 , model1.rowCount()): layerName = model1.item(i,2).data(Qt.EditRole) if not layerName in qgisLayerNames: self.plugin.removeLayer(i) self.removeClosedLayers(model1) break def calculatePointProfile(self, point, model, library): self.model = model self.library = library statName = self.getPointProfileStatNames()[0] self.removeClosedLayers(model) if point == None: return PlottingTool().clearData(self.dockwidget, model, library) self.profiles = [] #creating the plots of profiles for i in range(0 , model.rowCount()): self.profiles.append( {"layer": model.item(i,3).data(Qt.EditRole) } ) self.profiles[i][statName] = [] self.profiles[i]["l"] = [] layer = self.profiles[i]["layer"] if layer: try: ident = layer.dataProvider().identify(point, QgsRaster.IdentifyFormatValue ) except: ident = None else: ident = None if ident is not None: self.profiles[i][statName] = list(ident.results().values()) self.profiles[i]["l"] = list(ident.results().keys()) self.setXAxisSteps() PlottingTool().attachCurves(self.dockwidget, self.profiles, model, library) if self.dockwidget.cboAutoScale.isChecked(): PlottingTool().reScalePlot(self.dockwidget, self.profiles, model, library) self.setupTableTab(model) def getPointProfileStatNames(self): return ["value"] # The code is based on the approach of ZonalStatistics from Processing toolbox def calculatePolygonProfile(self, geometry, crs, model, library): self.model = model self.library = library self.removeClosedLayers(model) if geometry is None or geometry.isEmpty(): return PlottingTool().clearData(self.dockwidget, model, library) self.profiles = [] #creating the plots of profiles for i in range(0 , model.rowCount()): self.profiles.append( {"layer": model.item(i,3).data(Qt.EditRole) } ) self.profiles[i]["l"] = [] for statistic in self.getPolygonProfileStatNames(): self.profiles[i][statistic] = [] # Get intersection between polygon geometry and raster following ZonalStatistics code rasterDS = gdal.Open(self.profiles[i]["layer"].source(), gdal.GA_ReadOnly) geoTransform = rasterDS.GetGeoTransform() cellXSize = abs(geoTransform[1]) cellYSize = abs(geoTransform[5]) rasterXSize = rasterDS.RasterXSize rasterYSize = rasterDS.RasterYSize rasterBBox = QgsRectangle(geoTransform[0], geoTransform[3] - cellYSize * rasterYSize, geoTransform[0] + cellXSize * rasterXSize, geoTransform[3]) rasterGeom = QgsGeometry.fromRect(rasterBBox) memVectorDriver = ogr.GetDriverByName('Memory') memRasterDriver = gdal.GetDriverByName('MEM') intersectedGeom = rasterGeom.intersection(geometry) ogrGeom = ogr.CreateGeometryFromWkt(intersectedGeom.asWkt()) bbox = intersectedGeom.boundingBox() xMin = bbox.xMinimum() xMax = bbox.xMaximum() yMin = bbox.yMinimum() yMax = bbox.yMaximum() (startColumn, startRow) = self.mapToPixel(xMin, yMax, geoTransform) (endColumn, endRow) = self.mapToPixel(xMax, yMin, geoTransform) width = endColumn - startColumn height = endRow - startRow if width == 0 or height == 0: return srcOffset = (startColumn, startRow, width, height) newGeoTransform = ( geoTransform[0] + srcOffset[0] * geoTransform[1], geoTransform[1], 0.0, geoTransform[3] + srcOffset[1] * geoTransform[5], 0.0, geoTransform[5], ) # Create a temporary vector layer in memory memVDS = memVectorDriver.CreateDataSource('out') memLayer = memVDS.CreateLayer('poly', crs, ogr.wkbPolygon) ft = ogr.Feature(memLayer.GetLayerDefn()) ft.SetGeometry(ogrGeom) memLayer.CreateFeature(ft) ft.Destroy() # Rasterize it rasterizedDS = memRasterDriver.Create('', srcOffset[2], srcOffset[3], 1, gdal.GDT_Byte) rasterizedDS.SetGeoTransform(newGeoTransform) gdal.RasterizeLayer(rasterizedDS, [1], memLayer, burn_values=[1]) rasterizedArray = rasterizedDS.ReadAsArray() for bandNumber in range(1, rasterDS.RasterCount+1): rasterBand = rasterDS.GetRasterBand(bandNumber) noData = rasterBand.GetNoDataValue() if noData is None: noData = np.nan scale = rasterBand.GetScale() if scale is None: scale = 1.0 offset = rasterBand.GetOffset() if offset is None: offset = 0.0 srcArray = rasterBand.ReadAsArray(*srcOffset) srcArray = srcArray*scale+offset masked = np.ma.MaskedArray(srcArray, mask=np.logical_or.reduce(( srcArray == noData, np.logical_not(rasterizedArray), np.isnan(srcArray)))) self.profiles[i]["l"].append(bandNumber) self.profiles[i]["count"].append(float(masked.count())) self.profiles[i]["max"].append(float(masked.max())) self.profiles[i]["mean"].append(float(masked.mean())) self.profiles[i]["median"].append(float(np.ma.median(masked))) self.profiles[i]["min"].append(float(masked.min())) self.profiles[i]["range"].append(float(masked.max()) - float(masked.min())) self.profiles[i]["std"].append(float(masked.std())) self.profiles[i]["sum"].append(float(masked.sum())) self.profiles[i]["unique"].append(np.unique(masked.compressed()).size) self.profiles[i]["var"].append(float(masked.var())) memVDS = None rasterizedDS = None rasterDS = None self.setXAxisSteps() PlottingTool().attachCurves(self.dockwidget, self.profiles, model, library) if self.dockwidget.cboAutoScale.isChecked(): PlottingTool().reScalePlot(self.dockwidget, self.profiles, model, library) self.setupTableTab(model) def getPolygonProfileStatNames(self): return ["count", "max", "mean", "median", "min", "range", "std", "sum", "unique", "var"] def setXAxisSteps(self): if self.xAxisSteps == None: self.changeXAxisStepType("numeric") return elif self.xAxisSteps[0] == "Timesteps": for profile in self.profiles: stepsNum = len(profile["l"]) startTime = self.xAxisSteps[1] step = self.xAxisSteps[2] stepType = self.xAxisSteps[3] useNetcdfTime = self.xAxisSteps[4] if stepType == "years": stepType = "days" step = step * 365 elif stepType == "months": stepType = "days" step = step * 365/12 profile["l"] = [] if useNetcdfTime and (profile["layer"].source().startswith("NETCDF:") or profile["layer"].source().endswith(".nc")): try: import netCDF4 if profile["layer"].source().startswith("NETCDF:"): filename = re.match('NETCDF:\"(.*)\":.*$', profile["layer"].source()).group(1) else: filename = profile["layer"].source() nc = netCDF4.Dataset(filename, mode='r') profile["l"] = netCDF4.num2date(nc.variables["time"][:], units = nc.variables["time"].units, calendar = nc.variables["time"].calendar) nc.close() except ImportError: text = "Temporal/Spectral Profile Tool: netCDF4 module is required to read NetCDF " + \ "time dimension. Please use pip install netCDF4" self.iface.messageBar().pushWidget(self.iface.messageBar().createMessage(text), QgsMessageBar.WARNING, 5) profile["l"] = [] except KeyError: text = "Temporal/Spectral Profile Tool: NetCDF file does not have " + \ "time dimension." self.iface.messageBar().pushWidget(self.iface.messageBar().createMessage(text), QgsMessageBar.WARNING, 5) nc.close() profile["l"] = [] if profile["l"] == []: for i in range(stepsNum): timedeltaParams = {stepType: step*i} profile["l"].append(startTime + timedelta(**timedeltaParams)) self.changeXAxisStepType("timedate") else: for profile in self.profiles: # Truncate the profiles to the minimum of the length of each profile # or length of provided x-axis steps stepsNum = min(len(self.xAxisSteps), len(profile["l"])) profile["l"] = self.xAxisSteps[:stepsNum] for stat in list(profile.keys()): if stat == "l" or stat == "layer": continue profile[stat] = profile[stat][:stepsNum] # If any x-axis step is a NaN then remove the corresponding # value from profile nans = [i for i, x in enumerate(profile["l"]) if math.isnan(x)] for stat in list(profile.keys()): if stat == "layer": continue profile[stat] = [x for i, x in enumerate(profile[stat]) if i not in nans] self.changeXAxisStepType("numeric") def changeXAxisStepType(self, newType): if self.xAxisStepType == newType: return else: self.xAxisStepType = newType PlottingTool().resetAxis(self.dockwidget, self.library) def mapToPixel(self, mX, mY, geoTransform): (pX, pY) = gdal.ApplyGeoTransform( gdal.InvGeoTransform(geoTransform), mX, mY) return (int(pX), int(pY)) def setupTableTab(self, model1): #*********************** TAble tab ************************************************* try: #Reinitializing the table tab self.VLayout = self.dockwidget.scrollAreaWidgetContents.layout() while 1: child = self.VLayout.takeAt(0) if not child: break child.widget().deleteLater() except: self.VLayout = QVBoxLayout(self.dockwidget.scrollAreaWidgetContents) self.VLayout.setContentsMargins(9, -1, -1, -1) #Setup the table tab self.groupBox = [] self.profilePushButton = [] self.tableView = [] self.verticalLayout = [] for i in range(0 , model1.rowCount()): self.groupBox.append( QGroupBox(self.dockwidget.scrollAreaWidgetContents) ) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox[i].sizePolicy().hasHeightForWidth()) self.groupBox[i].setSizePolicy(sizePolicy) self.groupBox[i].setMinimumSize(QSize(0, 150)) self.groupBox[i].setMaximumSize(QSize(16777215, 350)) self.groupBox[i].setTitle(QApplication.translate("GroupBox" + str(i), self.profiles[i]["layer"].name(), None)) self.groupBox[i].setObjectName("groupBox" + str(i)) self.verticalLayout.append( QVBoxLayout(self.groupBox[i]) ) self.verticalLayout[i].setObjectName("verticalLayout") #The table self.tableView.append( QTableView(self.groupBox[i]) ) self.tableView[i].setObjectName("tableView" + str(i)) font = QFont("Arial", 8) columns = len(self.profiles[i]["l"]) rowNames = list(self.profiles[i].keys()) rowNames.remove("layer") # holds the QgsMapLayer instance rowNames.remove("l") # holds the band number rows = len(rowNames) self.mdl = QStandardItemModel(rows+1, columns) self.mdl.setVerticalHeaderLabels(["band"] + rowNames) for j in range(columns): self.mdl.setData(self.mdl.index(0, j, QModelIndex()), str(self.profiles[i]["l"][j])) self.mdl.setData(self.mdl.index(0, j, QModelIndex()), font ,Qt.FontRole) for k in range(rows): self.mdl.setData(self.mdl.index(k+1, j, QModelIndex()), str(self.profiles[i][rowNames[k]][j])) self.mdl.setData(self.mdl.index(k+1, j, QModelIndex()), font ,Qt.FontRole) #self.tableView[i].setVerticalHeaderLabels(rowNames) self.tableView[i].verticalHeader().setDefaultSectionSize(18) self.tableView[i].horizontalHeader().setDefaultSectionSize(60) self.tableView[i].setModel(self.mdl) self.verticalLayout[i].addWidget(self.tableView[i]) self.horizontalLayout = QHBoxLayout() #the copy to clipboard button self.profilePushButton.append( QPushButton(self.groupBox[i]) ) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.profilePushButton[i].sizePolicy().hasHeightForWidth()) self.profilePushButton[i].setSizePolicy(sizePolicy) self.profilePushButton[i].setText(QApplication.translate("GroupBox", "Copy to clipboard", None)) self.profilePushButton[i].setObjectName(str(i)) self.horizontalLayout.addWidget(self.profilePushButton[i]) self.horizontalLayout.addStretch(0) self.verticalLayout[i].addLayout(self.horizontalLayout) self.VLayout.addWidget(self.groupBox[i]) self.profilePushButton[i].clicked.connect(self.copyTable) def copyTable(self): #Writing the table to clipboard in excel form nr = int( self.sender().objectName() ) self.clipboard = QApplication.clipboard() text = "band" rowNames = list(self.profiles[nr].keys()) rowNames.remove("layer") rowNames.remove("l") for name in rowNames: text += "\t"+name text += "\n" for i in range( len(self.profiles[nr]["l"]) ): text += str(self.profiles[nr]["l"][i]) for j in range(len(rowNames)): text += "\t" + str(self.profiles[nr][rowNames[j]][i]) text += "\n" self.clipboard.setText(text) def reScalePlot(self, param): # called when a spinbox value changed if type(param) != float: # don't execute it twice, for both valueChanged(int) and valueChanged(str) signals return if self.dockwidget.sbMinVal.value() == self.dockwidget.sbMaxVal.value() == 0: # don't execute it on init return PlottingTool().reScalePlot(self.dockwidget, self.profiles, self.model, self.library, autoMode = False)
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 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 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 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