Ejemplo n.º 1
0
 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)
Ejemplo n.º 2
0
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_()
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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']
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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]
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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()
Ejemplo n.º 10
0
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()
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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))
Ejemplo n.º 14
0
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