class CreateGroupPartyOperation(QDialog, DIALOG_UI):
    WIZARD_NAME = "CreateGroupPartyOperationWizard"
    WIZARD_TOOL_NAME = QCoreApplication.translate(WIZARD_NAME,
                                                  "Create group party")

    def __init__(self, iface, db, qgis_utils, parent=None):
        QDialog.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self._db = db
        self.qgis_utils = qgis_utils
        self.logger = Logger()
        self.names = self._db.names
        self.help_strings = HelpStrings()

        self.data = {}  # {t_id: [display_text, denominator, numerator]}
        self.current_selected_parties = []  #  [t_ids]
        self.parties_to_group = {}  # {t_id: [denominator, numerator]}

        self._layers = {
            self.names.OP_GROUP_PARTY_T: {
                'name': self.names.OP_GROUP_PARTY_T,
                'geometry': None,
                LAYER: None
            },
            self.names.OP_PARTY_T: {
                'name': self.names.OP_PARTY_T,
                'geometry': None,
                LAYER: None
            },
            self.names.MEMBERS_T: {
                'name': self.names.MEMBERS_T,
                'geometry': None,
                LAYER: None
            },
            self.names.FRACTION_S: {
                'name': self.names.FRACTION_S,
                'geometry': None,
                LAYER: None
            },
            self.names.COL_GROUP_PARTY_TYPE_D: {
                'name': self.names.COL_GROUP_PARTY_TYPE_D,
                'geometry': None,
                LAYER: None
            }
        }

        # Fill combo of types
        col_group_party_type_table = self.qgis_utils.get_layer(
            self._db, self.names.COL_GROUP_PARTY_TYPE_D, None, True)
        if not col_group_party_type_table:
            return

        for feature in col_group_party_type_table.getFeatures():
            self.cbo_group_type.addItem(feature[self.names.DISPLAY_NAME_F],
                                        feature[self.names.T_ID_F])

        self.txt_search_party.setText("")
        self.btn_select.setEnabled(False)
        self.btn_deselect.setEnabled(False)

        self.tbl_selected_parties.setColumnCount(3)
        self.tbl_selected_parties.setColumnWidth(0, 140)
        self.tbl_selected_parties.setColumnWidth(1, 90)
        self.tbl_selected_parties.setColumnWidth(2, 90)
        self.tbl_selected_parties.sortItems(0, Qt.AscendingOrder)

        self.txt_search_party.textEdited.connect(self.search)
        self.lst_all_parties.itemSelectionChanged.connect(
            self.selection_changed_all)
        self.tbl_selected_parties.itemSelectionChanged.connect(
            self.selection_changed_selected)
        self.tbl_selected_parties.cellChanged.connect(self.valueEdited)
        self.btn_select_all.clicked.connect(self.select_all)
        self.btn_deselect_all.clicked.connect(self.deselect_all)
        self.btn_select.clicked.connect(self.select)
        self.btn_deselect.clicked.connect(self.deselect)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)
        self.rejected.connect(self.close_wizard)

    def closeEvent(self, e):
        # It's necessary to prevent message bar alert
        pass

    def required_layers_are_available(self):
        layers_are_available = self.qgis_utils.required_layers_are_available(
            self._db, self._layers, self.WIZARD_TOOL_NAME)
        return layers_are_available

    def load_parties_data(self):
        expression = QgsExpression(
            LayerConfig.get_dict_display_expressions(
                self.names)[self.names.OP_PARTY_T])
        context = QgsExpressionContext()
        data = dict()
        for feature in self._layers[
                self.names.OP_PARTY_T][LAYER].getFeatures():
            context.setFeature(feature)
            expression.prepare(context)
            value = expression.evaluate(context)
            data[feature[self.names.T_ID_F]] = [
                value if value != NULL else None, 0, 0
            ]
        self.set_parties_data(data)

    def set_parties_data(self, parties_data):
        """
        Initialize parties data.

        :param parties_data: Dictionary {t_id: [display_text, denominator, numerator]}
        :type parties_data: dict
        """
        self.data = parties_data
        self.update_lists()

    def search(self, text):
        self.update_lists(True)

    def selection_changed_all(self):
        self.btn_select.setEnabled(len(self.lst_all_parties.selectedItems()))

    def selection_changed_selected(self):
        self.btn_deselect.setEnabled(
            len(self.tbl_selected_parties.selectedItems()))

    def select_all(self):
        """
        SLOT. Select all parties listed from left list widget.
        """
        items_ids = []
        for index in range(self.lst_all_parties.count()):
            items_ids.append(
                self.lst_all_parties.item(index).data(Qt.UserRole))
        self.add_parties_to_selected(items_ids)

    def deselect_all(self):
        """
        SLOT. Remove all parties from left list widget.
        """
        items_ids = []
        for index in range(self.tbl_selected_parties.rowCount()):
            items_ids.append(
                self.tbl_selected_parties.item(index, 0).data(Qt.UserRole))
        self.remove_parties_from_selected(items_ids)

    def select(self):
        """
        SLOT. Select all parties highlighted in left list widget.
        """
        self.add_parties_to_selected([
            item.data(Qt.UserRole)
            for item in self.lst_all_parties.selectedItems()
        ])

    def deselect(self):
        """
        SLOT. Remove all parties highlighted in right list widget.
        """
        self.remove_parties_from_selected([
            item.data(Qt.UserRole)
            for item in self.tbl_selected_parties.selectedItems()
            if item.column() == 0
        ])

    def add_parties_to_selected(self, parties_ids):
        self.current_selected_parties.extend(parties_ids)
        self.update_lists()

    def remove_parties_from_selected(self, parties_ids):
        for party_id in parties_ids:
            self.current_selected_parties.remove(party_id)
            if party_id in self.parties_to_group:
                del self.parties_to_group[party_id]
        self.update_lists()

    def update_lists(self, only_update_all_list=False):
        """
        Update left list widget and optionally the right one.

        :param only_update_all_list: Only update left list widget.
        :type only_update_all_list: bool
        """
        # All parties
        self.lst_all_parties.clear()
        if self.txt_search_party.text():
            tmp_parties = {
                i: d
                for i, d in self.data.items()
                if self.txt_search_party.text().lower() in d[0].lower()
            }
        else:
            tmp_parties = copy.deepcopy(self.data)  # Copy all!

        for party_id in self.current_selected_parties:
            if party_id in tmp_parties:
                del tmp_parties[party_id]

        for i, d in tmp_parties.items():
            item = QListWidgetItem(d[0])
            item.setData(Qt.UserRole, i)
            self.lst_all_parties.addItem(item)

        if not only_update_all_list:
            # Selected parties
            self.tbl_selected_parties.clearContents()
            self.tbl_selected_parties.setRowCount(
                len(self.current_selected_parties))
            self.tbl_selected_parties.setColumnCount(3)
            self.tbl_selected_parties.setSortingEnabled(False)

            for row, party_id in enumerate(self.current_selected_parties):
                item = QTableWidgetItem(self.data[party_id][0])
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                item.setData(Qt.UserRole, party_id)
                self.tbl_selected_parties.setItem(row, 0, item)
                value_denominator = self.parties_to_group[party_id][
                    0] if party_id in self.parties_to_group else self.data[
                        party_id][1]
                self.tbl_selected_parties.setItem(
                    row, 1, QTableWidgetItem(str(value_denominator)))
                value_numerator = self.parties_to_group[party_id][
                    1] if party_id in self.parties_to_group else self.data[
                        party_id][2]
                self.tbl_selected_parties.setItem(
                    row, 2, QTableWidgetItem(str(value_numerator)))

            self.tbl_selected_parties.setSortingEnabled(True)

    def valueEdited(self, row, column):
        """
        SLOT. Update either the denominator or the numerator for given row.

        :param row: Edited row
        :type row: int
        :param column: Edited column
        :type column: int
        """
        if column != 0:
            party_id = self.tbl_selected_parties.item(row, 0).data(Qt.UserRole)
            value_denominator = self.tbl_selected_parties.item(row, 1).text()

            # While creating a row and the second column is created, the third
            # one doesn't exist, so use the value already stored for that case
            value_numerator = self.parties_to_group[party_id][
                1] if party_id in self.parties_to_group else 0
            if self.tbl_selected_parties.item(row, 2) is not None:
                value_numerator = self.tbl_selected_parties.item(row, 2).text()

            self.parties_to_group[party_id] = [
                value_denominator, value_numerator
            ]

    def accept(self):
        """ Overwrite the dialog's `accept
        <https://doc.qt.io/qt-5/qdialog.html#accept>`_ SLOT to store
        selected parties and numerator-denominator before closing the dialog.
        """
        self.parties_to_group = {}
        for index in range(self.tbl_selected_parties.rowCount()):
            k = self.tbl_selected_parties.item(index, 0).data(Qt.UserRole)
            try:
                v_n = int(self.tbl_selected_parties.item(index, 1).text())
            except ValueError as e:
                self.show_message(
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "There are some invalid values in the numerator column. Fix them before continuing..."
                    ), Qgis.Warning)
                return
            try:
                v_d = int(self.tbl_selected_parties.item(index, 2).text())
            except ValueError as e:
                self.show_message(
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "There are some invalid values in the denominator column. Fix them before continuing..."
                    ), Qgis.Warning)
                return

            self.parties_to_group[k] = [v_n, v_d]

        name = self.txt_group_name.text()
        group_party_type = self.cbo_group_type.itemData(
            self.cbo_group_type.currentIndex())
        dict_params = {
            self.names.COL_PARTY_T_NAME_F: name,
            self.names.COL_GROUP_PARTY_T_TYPE_F: group_party_type,
            'porcentajes': self.parties_to_group
        }

        res, msg = self.validate_group_party(dict_params)

        if not res:
            self.show_message(msg, Qgis.Warning)
            return

        self.save_group_party(self._db, [dict_params])

    def validate_group_party(self, params):
        name = params[self.names.COL_PARTY_T_NAME_F]
        group_party_type = params[self.names.COL_GROUP_PARTY_T_TYPE_F]
        porcentajes = params['porcentajes']

        if not porcentajes:
            return (False,
                    QCoreApplication.translate(
                        "CreateGroupParty",
                        "You need to select some parties to create a group."))
        elif len(porcentajes) == 1:
            return (
                False,
                QCoreApplication.translate(
                    "CreateGroupParty",
                    "There is just one party, you need to add at least two parties to a group."
                ))

        there_percents = False
        fraction = Fraction()
        for t, nd in porcentajes.items():
            if porcentajes[t] != [0, 0]:
                there_percents = True
                break

        if there_percents:
            for t, nd in porcentajes.items():
                if porcentajes[t][1] == 0:
                    return (
                        False,
                        QCoreApplication.translate(
                            "CreateGroupParty",
                            "There are denominators equal to zero. You need to change those values."
                        ))
                elif porcentajes[t][1] < porcentajes[t][0]:
                    return (
                        False,
                        QCoreApplication.translate(
                            "CreateGroupParty",
                            "The denominator cannot be less than the numerator."
                        ))
                else:
                    fraction = Fraction(porcentajes[t][0],
                                        porcentajes[t][1]) + fraction
            if fraction != 1.0:
                return (False,
                        QCoreApplication.translate(
                            "CreateGroupParty",
                            "The sum of the fractions must be equal to one."))

        return (True,
                QCoreApplication.translate("CreateGroupParty",
                                           "Validation passed!"))

    def show_message(self, message, level):
        self.bar.clearWidgets(
        )  # Remove previous messages before showing a new one
        self.bar.pushMessage(message, level, 10)

    def save_group_party(self, db, params):
        """
        Save group party data into associated tables: self.names.OP_GROUP_PARTY_T,
        self.names.MEMBERS_T and self.names.FRACTION_S.

        params: List of dicts, where each dict is an independent group party:
            {
                self.names.COL_PARTY_T_NAME_F: '',
                self.names.COL_GROUP_PARTY_T_TYPE_F: '',
                'porcentajes': {
                    't_id_miembro': [20, 100], # numerador/denominador
                    't_id_miembro2': [40, 100]
                }
            }
        """
        # Disconnect from previous runs
        self.disconnect_signals()

        for group in params:
            # Create connections to react when a group party is stored to the DB
            self._layers[self.names.OP_GROUP_PARTY_T][
                LAYER].committedFeaturesAdded.connect(
                    partial(self.finish_group_party_saving,
                            group['porcentajes']))

            # First save the group party
            new_feature = QgsVectorLayerUtils().createFeature(
                self._layers[self.names.OP_GROUP_PARTY_T][LAYER])
            new_feature.setAttribute(
                self.names.COL_GROUP_PARTY_T_TYPE_F,
                group[self.names.COL_GROUP_PARTY_T_TYPE_F])
            new_feature.setAttribute(self.names.COL_PARTY_T_NAME_F,
                                     group[self.names.COL_PARTY_T_NAME_F])

            # TODO: Remove when local id and working space are defined
            new_feature.setAttribute(self.names.OID_T_LOCAL_ID_F, 1)
            new_feature.setAttribute(self.names.OID_T_NAMESPACE_F,
                                     self.names.OP_GROUP_PARTY_T)

            # TODO: Gui should allow users to ented namespace, local_id and date values
            #new_feature.setAttribute("p_espacio_de_nombres", self.names.OP_GROUP_PARTY_T)
            #new_feature.setAttribute("p_local_id", '0')
            #new_feature.setAttribute("comienzo_vida_util_version", 'now()')

            self.logger.info(__name__, "Saving Group Party: {}".format(group))
            with edit(self._layers[self.names.OP_GROUP_PARTY_T][LAYER]):
                self._layers[self.names.OP_GROUP_PARTY_T][LAYER].addFeature(
                    new_feature)

    def finish_group_party_saving(self, members, layer_id, features):
        try:
            self._layers[self.names.OP_GROUP_PARTY_T][
                LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass

        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed because an error occurred while trying to save the data."
        ).format(self.WIZARD_TOOL_NAME)
        if len(features) != 1:
            message = QCoreApplication.translate(
                "WizardTranslations",
                "'{}' tool has been closed. We should have got only one group party... We cannot do anything with {} group parties"
            ).format(self.WIZARD_TOOL_NAME, len(features))
            self.logger.warning(
                __name__,
                "We should have got only one group party... We cannot do anything with {} group parties"
                .format(len(features)))
        else:
            fid = features[0].id()
            if not self._layers[self.names.OP_GROUP_PARTY_T][LAYER].getFeature(
                    fid).isValid():
                self.logger.warning(
                    __name__, "Feature not found in table Group Party...")
            else:
                group_party_id = self._layers[self.names.OP_GROUP_PARTY_T][
                    LAYER].getFeature(fid)[self.names.T_ID_F]

                # Now save members
                party_ids = list()
                for party_id, fraction in members.items():
                    # Create connections to react when a group party is stored to the DB
                    self._layers[self.names.MEMBERS_T][
                        LAYER].committedFeaturesAdded.connect(
                            partial(self.finish_member_saving, fraction))

                    new_feature = QgsVectorLayerUtils().createFeature(
                        self._layers[self.names.MEMBERS_T][LAYER])
                    new_feature.setAttribute(
                        self.names.MEMBERS_T_GROUP_PARTY_F, group_party_id)
                    new_feature.setAttribute(self.names.MEMBERS_T_PARTY_F,
                                             party_id)
                    self.logger.info(
                        __name__,
                        "Saving group party's member ({}: {}).".format(
                            group_party_id, party_id))
                    with edit(self._layers[self.names.MEMBERS_T][LAYER]):
                        self._layers[self.names.MEMBERS_T][LAYER].addFeature(
                            new_feature)
                        party_ids.append(party_id)

                if len(party_ids):
                    message = QCoreApplication.translate(
                        "WizardTranslations",
                        "The new group party (t_id={}) was successfully created and associated with its corresponding party(ies) (t_id={})!"
                    ).format(group_party_id,
                             ", ".join([str(b) for b in party_ids]))
                else:
                    message = QCoreApplication.translate(
                        "WizardTranslations",
                        "The new group party (t_id={}) was successfully created but this one wasn't associated with a party(ies)"
                    ).format(group_party_id)
        self.close_wizard(message)

    def finish_member_saving(self, fraction, layer_id, features):
        try:
            self._layers[self.names.
                         MEMBERS_T][LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass

        if len(features) != 1:
            self.logger.warning(
                __name__,
                "We should have got only one member... We cannot do anything with {} members"
                .format(len(features)))
        else:
            fid = features[0].id()
            if not self._layers[self.names.MEMBERS_T][LAYER].getFeature(
                    fid).isValid():
                self.logger.warning(__name__,
                                    "Feature not found in table Members...")
            else:
                member_id = self._layers[self.names.MEMBERS_T][
                    LAYER].getFeature(fid)[self.names.T_ID_F]

                if fraction == [0, 0]:
                    return

                # And finally save fractions
                new_feature = QgsVectorLayerUtils().createFeature(
                    self._layers[self.names.FRACTION_S][LAYER])
                new_feature.setAttribute(self.names.FRACTION_S_MEMBER_F,
                                         member_id)
                new_feature.setAttribute(self.names.FRACTION_S_NUMERATOR_F,
                                         fraction[0])
                new_feature.setAttribute(self.names.FRACTION_S_DENOMINATOR_F,
                                         fraction[1])
                with edit(self._layers[self.names.FRACTION_S][LAYER]):
                    self.logger.info(
                        __name__, "Saving member's fraction ({}: {}).".format(
                            member_id, fraction))
                    self._layers[self.names.FRACTION_S][LAYER].addFeature(
                        new_feature)

    def close_wizard(self, message=None, show_message=True):
        if message is None:
            message = QCoreApplication.translate(
                "WizardTranslations",
                "'{}' tool has been closed.").format(self.WIZARD_TOOL_NAME)
        if show_message:
            self.logger.info_msg(__name__, message)
        self.disconnect_signals()
        self.close()

    def disconnect_signals(self):
        try:
            self._layers[self.names.OP_GROUP_PARTY_T][
                LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass
        try:
            self._layers[self.names.
                         MEMBERS_T][LAYER].committedFeaturesAdded.disconnect()
        except TypeError as e:
            pass

    def show_help(self):
        self.qgis_utils.show_help("group_party")
class CreatePointsSurveyWizard(QWizard, WIZARD_UI):
    WIZARD_NAME = "CreatePointsSurveyWizard"
    WIZARD_TOOL_NAME = QCoreApplication.translate(WIZARD_NAME, "Create Point")

    def __init__(self, iface, db):
        QWizard.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self._db = db
        self.logger = Logger()
        self.app = AppInterface()

        self.names = self._db.names
        self.help_strings = HelpStrings()

        self._layers = {
            self.names.LC_BOUNDARY_POINT_T: None,
            self.names.LC_SURVEY_POINT_T: None,
            self.names.LC_CONTROL_POINT_T: None
        }

        self.target_layer = None

        # Auxiliary data to set nonlinear next pages
        self.pages = [self.wizardPage1, self.wizardPage2, self.wizardPage3]
        self.dict_pages_ids = {self.pages[idx] : pid for idx, pid in enumerate(self.pageIds())}

        self.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PointLayer)

        # Set connections
        self.btn_browse_file.clicked.connect(
            make_file_selector(self.txt_file_path,
                               file_filter=QCoreApplication.translate("WizardTranslations",'CSV File (*.csv *.txt)')))
        self.txt_file_path.textChanged.connect(self.file_path_changed)
        self.crsSelector.crsChanged.connect(self.crs_changed)
        self.crs = ""  # SRS auth id
        self.txt_delimiter.textChanged.connect(self.fill_long_lat_combos)
        self.mMapLayerComboBox.layerChanged.connect(self.import_layer_changed)

        self.known_delimiters = [
            {'name': ';', 'value': ';'},
            {'name': ',', 'value': ','},
            {'name': 'tab', 'value': '\t'},
            {'name': 'space', 'value': ' '},
            {'name': '|', 'value': '|'},
            {'name': '~', 'value': '~'},
            {'name': 'Other', 'value': ''}
        ]
        self.cbo_delimiter.addItems([ item['name'] for item in self.known_delimiters ])
        self.cbo_delimiter.currentTextChanged.connect(self.separator_changed)

        self.restore_settings()

        self.txt_file_path.textChanged.emit(self.txt_file_path.text())

        self.rad_boundary_point.toggled.connect(self.point_option_changed)
        self.rad_control_point.toggled.connect(self.point_option_changed)
        self.rad_csv.toggled.connect(self.adjust_page_2_controls)
        self.point_option_changed() # Initialize it
        self.button(QWizard.FinishButton).clicked.connect(self.finished_dialog)
        self.currentIdChanged.connect(self.current_page_changed)

        self.txt_help_page_2.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_2_OPTION_CSV)

        self.wizardPage2.setButtonText(QWizard.FinishButton,
                                       QCoreApplication.translate("WizardTranslations",
                                            "Import"))
        self.txt_help_page_3.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_3_OPTION_CSV)
        self.txt_help_page_3.anchorClicked.connect(self.save_template)
        self.button(QWizard.HelpButton).clicked.connect(self.show_help)
        self.rejected.connect(self.close_wizard)

        # Set MessageBar for QWizard
        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.setLayout(QGridLayout())
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

    def nextId(self):
        """
        Set navigation order. Should return an integer. -1 is Finish.
        """
        if self.currentId() == self.dict_pages_ids[self.wizardPage1]:
            return self.dict_pages_ids[self.wizardPage2]
        elif self.currentId() == self.dict_pages_ids[self.wizardPage2]:
            if self.rad_csv.isChecked():
                return self.dict_pages_ids[self.wizardPage3]
            elif self.rad_refactor.isChecked():
                return -1
        elif self.currentId() == self.dict_pages_ids[self.wizardPage3]:
            return -1
        else:
            return -1

    def current_page_changed(self, id):
        """
        Reset the Next button. Needed because Next might have been disabled by a
        condition in a another SLOT.
        """
        enable_next_wizard(self)

        if id == self.dict_pages_ids[self.wizardPage2]:
            self.adjust_page_2_controls()
        elif id == self.dict_pages_ids[self.wizardPage3]:
            self.set_buttons_visible(False)
            self.set_buttons_enabled(False)

            QCoreApplication.processEvents()
            self.check_z_in_geometry()
            QCoreApplication.processEvents()
            self.fill_long_lat_combos("")
            QCoreApplication.processEvents()

            self.set_buttons_visible(True)
            self.set_buttons_enabled(True)

    def set_buttons_visible(self, visible):
        self.button(self.BackButton).setVisible(visible)
        self.button(self.FinishButton).setVisible(visible)
        self.button(self.CancelButton).setVisible(visible)

    def set_buttons_enabled(self, enabled):
        self.wizardPage3.setEnabled(enabled)
        self.button(self.BackButton).setEnabled(enabled)
        self.button(self.FinishButton).setEnabled(enabled)
        self.button(self.CancelButton).setEnabled(enabled)

    def check_z_in_geometry(self):
        self.target_layer = self.app.core.get_layer(self._db, self.current_point_name(), load=True)
        if not self.target_layer:
            return

        if not QgsWkbTypes().hasZ(self.target_layer.wkbType()):
            self.labelZ.setEnabled(False)
            self.cbo_elevation.setEnabled(False)
            msg = QCoreApplication.translate("WizardTranslations",
                                             "The current model does not support 3D geometries")
            self.cbo_elevation.setToolTip(msg)
            self.labelZ.setToolTip(msg)
        else:
            self.labelZ.setEnabled(True)
            self.cbo_elevation.setEnabled(True)
            self.labelZ.setToolTip("")
            self.cbo_elevation.setToolTip("")

    def adjust_page_2_controls(self):
        self.cbo_mapping.clear()
        self.cbo_mapping.addItem("")
        self.cbo_mapping.addItems(self.app.core.get_field_mappings_file_names(self.current_point_name()))

        if self.rad_refactor.isChecked():
            self.lbl_refactor_source.setEnabled(True)
            self.mMapLayerComboBox.setEnabled(True)
            self.lbl_field_mapping.setEnabled(True)
            self.cbo_mapping.setEnabled(True)
            self.import_layer_changed(self.mMapLayerComboBox.currentLayer())

            disable_next_wizard(self)
            self.wizardPage2.setFinalPage(True)
            self.txt_help_page_2.setHtml(self.help_strings.get_refactor_help_string(self._db, self._layers[self.current_point_name()]))

        elif self.rad_csv.isChecked():
            self.lbl_refactor_source.setEnabled(False)
            self.mMapLayerComboBox.setEnabled(False)
            self.lbl_field_mapping.setEnabled(False)
            self.cbo_mapping.setEnabled(False)
            self.lbl_refactor_source.setStyleSheet('')

            enable_next_wizard(self)
            self.wizardPage2.setFinalPage(False)
            self.txt_help_page_2.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_2_OPTION_CSV)

    def point_option_changed(self):
        if self.rad_boundary_point.isChecked():
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Boundary Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Boundary Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_BP)
        elif self.rad_survey_point.isChecked(): # self.rad_survey_point is checked
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Survey Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Survey Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_SP)
        else: # self.rad_control_point is checked
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Control Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Control Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_CP)

    def finished_dialog(self):
        self.save_settings()

        if self.rad_refactor.isChecked():
            output_layer_name = self.current_point_name()

            if self.mMapLayerComboBox.currentLayer() is not None:
                field_mapping = self.cbo_mapping.currentText()
                res_etl_model = self.app.core.show_etl_model(self._db,
                                                               self.mMapLayerComboBox.currentLayer(),
                                                               output_layer_name,
                                                               field_mapping=field_mapping)

                if res_etl_model:
                    self.app.gui.redraw_all_layers()  # Redraw all layers to show imported data

                    # If the result of the etl_model is successful and we used a stored recent mapping, we delete the
                    # previous mapping used (we give preference to the latest used mapping)
                    if field_mapping:
                        self.app.core.delete_old_field_mapping(field_mapping)

                    self.app.core.save_field_mapping(output_layer_name)

            else:
                self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                    "Select a source layer to set the field mapping to '{}'.").format(output_layer_name))

            self.close_wizard()

        elif self.rad_csv.isChecked():
            self.prepare_copy_csv_points_to_db()

    def close_wizard(self, message=None, show_message=True):
        if message is None:
            message = QCoreApplication.translate("WizardTranslations", "'{}' tool has been closed.").format(self.WIZARD_TOOL_NAME)
        if show_message:
            self.logger.info_msg(__name__, message)
        self.close()

    def current_point_name(self):
        if self.rad_boundary_point.isChecked():
            return self.names.LC_BOUNDARY_POINT_T
        elif self.rad_survey_point.isChecked():
            return self.names.LC_SURVEY_POINT_T
        else:
            return self.names.LC_CONTROL_POINT_T

    def prepare_copy_csv_points_to_db(self):
        csv_path = self.txt_file_path.text().strip()

        if not csv_path or not os.path.exists(csv_path):
            self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                                                                         "No CSV file given or file doesn't exist."))
            return

        target_layer_name = self.current_point_name()

        with OverrideCursor(Qt.WaitCursor):
            csv_layer = self.app.core.csv_to_layer(csv_path,
                                                   self.txt_delimiter.text(),
                                                   self.cbo_longitude.currentText(),
                                                   self.cbo_latitude.currentText(),
                                                   self.crs,
                                                   self.cbo_elevation.currentText() or None,
                                                   self.detect_decimal_point(csv_path))

            self.app.core.copy_csv_to_db(csv_layer, self._db, target_layer_name)

    def required_layers_are_available(self):
        layers_are_available = self.app.core.required_layers_are_available(self._db, self._layers, self.WIZARD_TOOL_NAME)
        return layers_are_available

    def file_path_changed(self):
        self.autodetect_separator()
        self.fill_long_lat_combos("")
        self.cbo_delimiter.currentTextChanged.connect(self.separator_changed)

    def detect_decimal_point(self, csv_path):
        if os.path.exists(csv_path):
            with open(csv_path) as file:
                file.readline() # headers
                data = file.readline().strip() # 1st line with data

            if data:
                fields = self.get_fields_from_csv_file(csv_path)
                if self.cbo_latitude.currentText() in fields:
                    num_col = data.split(self.cbo_delimiter.currentText())[fields.index(self.cbo_latitude.currentText())]
                    for decimal_point in ['.', ',']:
                        if decimal_point in num_col:
                            return decimal_point

        return '.' # just use the default one

    def autodetect_separator(self):
        csv_path = self.txt_file_path.text().strip()
        if os.path.exists(csv_path):
            with open(csv_path) as file:
                first_line = file.readline()
                for delimiter in self.known_delimiters:
                    if delimiter['value'] == '':
                        continue
                    # if separator works like a column separator in header
                    # number of cols is greater than 1
                    if len(first_line.split(delimiter['value'])) > 1:
                        self.cbo_delimiter.setCurrentText(delimiter['name'])
                        return

    def update_crs_info(self):
        self.crsSelector.setCrs(QgsCoordinateReferenceSystem(self.crs))

    def crs_changed(self):
        self.crs = get_crs_authid(self.crsSelector.crs())
        if self.crs != DEFAULT_SRS_AUTHID:
            self.lbl_crs.setStyleSheet('color: orange')
            self.lbl_crs.setToolTip(QCoreApplication.translate("WizardTranslations",
                "Your CSV data will be reprojected for you to '{}' (Colombian National Origin),<br>before attempting to import it into LADM-COL.").format(DEFAULT_SRS_AUTHID))
        else:
            self.lbl_crs.setStyleSheet('')
            self.lbl_crs.setToolTip(QCoreApplication.translate("WizardTranslations", "Coordinate Reference System"))

    def fill_long_lat_combos(self, text):
        csv_path = self.txt_file_path.text().strip()
        self.cbo_longitude.clear()
        self.cbo_latitude.clear()
        self.cbo_elevation.clear()
        if os.path.exists(csv_path):
            self.button(QWizard.FinishButton).setEnabled(True)

            fields = self.get_fields_from_csv_file(csv_path)
            fields_dict = {field: field.lower() for field in fields}
            if not fields:
                self.button(QWizard.FinishButton).setEnabled(False)
                return

            self.cbo_longitude.addItems(fields)
            self.cbo_latitude.addItems(fields)
            self.cbo_elevation.addItems([""] + fields)

            # Heuristics to suggest values for x, y and z
            x_potential_names = ['x', 'lon', 'long', 'longitud', 'longitude', 'este', 'east', 'oeste', 'west']
            y_potential_names = ['y', 'lat', 'latitud', 'latitude', 'norte', 'north']
            z_potential_names = ['z', 'altura', 'elevacion', 'elevation', 'elevación', 'height']
            for x_potential_name in x_potential_names:
                for k,v in fields_dict.items():
                    if x_potential_name == v:
                        self.cbo_longitude.setCurrentText(k)
                        break
            for y_potential_name in y_potential_names:
                for k, v in fields_dict.items():
                    if y_potential_name == v:
                        self.cbo_latitude.setCurrentText(k)
                        break
            if self.cbo_elevation.isEnabled():
                for z_potential_name in z_potential_names:
                    for k, v in fields_dict.items():
                        if z_potential_name == v:
                            self.cbo_elevation.setCurrentText(k)
                            break

        else:
            self.button(QWizard.FinishButton).setEnabled(False)

    def get_fields_from_csv_file(self, csv_path):
        if not self.txt_delimiter.text():
            return []

        error_reading = False
        try:
            reader  = open(csv_path, "r")
        except IOError:
            error_reading = True
        line = reader.readline().replace("\n", "")
        reader.close()
        if not line:
            error_reading = True
        else:
            return line.split(self.txt_delimiter.text())

        if error_reading:
            self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                "It was not possible to read field names from the CSV. Check the file and try again."))
        return []

    def separator_changed(self, text):
        # first ocurrence
        value = next((x['value'] for x in self.known_delimiters if x['name'] == text), '')
        self.txt_delimiter.setText(value)
        if value == '':
            self.txt_delimiter.setEnabled(True)
        else:
            self.txt_delimiter.setEnabled(False)

    def save_template(self, url):
        link = url.url()
        if self.rad_boundary_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_boundary_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_boundary_points.csv')
        elif self.rad_survey_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_survey_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_survey_points.csv')
        elif self.rad_control_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_control_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_control_points.csv')

    def download_csv_file(self, filename):
        settings = QSettings()
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip())

        new_filename, filter = QFileDialog.getSaveFileName(self,
                                   QCoreApplication.translate("WizardTranslations",
                                                              "Save File"),
                                   os.path.join(settings.value('Asistente-LADM-COL/wizards/points_download_csv_path', '.'), filename),
                                   QCoreApplication.translate("WizardTranslations",
                                                              "CSV File (*.csv *.txt)"))

        if new_filename:
            settings.setValue('Asistente-LADM-COL/wizards/points_download_csv_path', os.path.dirname(new_filename))
            template_file = QFile(":/Asistente-LADM-COL/resources/csv/" + filename)

            if not template_file.exists():
                self.logger.critical(__name__, "CSV doesn't exist! Probably due to a missing 'make' execution to generate resources...")
                msg = QCoreApplication.translate("WizardTranslations", "CSV file not found. Update your plugin. For details see log.")
                self.show_message(msg, Qgis.Warning)
                return

            if os.path.isfile(new_filename):
                self.logger.info(__name__, 'Removing existing file {}...'.format(new_filename))
                os.chmod(new_filename, 0o777)
                os.remove(new_filename)

            if template_file.copy(new_filename):
                os.chmod(new_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
                msg = QCoreApplication.translate("WizardTranslations", """The file <a href="file:///{}">{}</a> was successfully saved!""").format(normalize_local_url(new_filename), os.path.basename(new_filename))
                self.show_message(msg, Qgis.Info)
            else:
                self.logger.warning(__name__, 'There was an error copying the CSV file {}!'.format(new_filename))
                msg = QCoreApplication.translate("WizardTranslations", "The file couldn\'t be saved.")
                self.show_message(msg, Qgis.Warning)

    def import_layer_changed(self, layer):
        if layer:
            crs = get_crs_authid(layer.crs())
            if crs != DEFAULT_SRS_AUTHID:
                self.lbl_refactor_source.setStyleSheet('color: orange')
                self.lbl_refactor_source.setToolTip(QCoreApplication.translate("WizardTranslations",
                                                                   "This layer will be reprojected for you to '{}' (Colombian National Origin),<br>before attempting to import it into LADM-COL.").format(
                    DEFAULT_SRS_AUTHID))
            else:
                self.lbl_refactor_source.setStyleSheet('')
                self.lbl_refactor_source.setToolTip('')

    def show_message(self, message, level):
        self.bar.clearWidgets()  # Remove previous messages before showing a new one
        self.bar.pushMessage(message, level, 10)

    def save_settings(self):
        settings = QSettings()
        point_type = None
        if self.rad_boundary_point.isChecked():
            point_type = 'boundary_point'
        elif self.rad_survey_point.isChecked():
            point_type = 'survey_point'
        else:
            point_type = 'control_point'

        settings.setValue('Asistente-LADM-COL/wizards/points_add_points_type', point_type)
        settings.setValue('Asistente-LADM-COL/wizards/points_load_data_type', 'csv' if self.rad_csv.isChecked() else 'refactor')
        settings.setValue('Asistente-LADM-COL/wizards/points_add_points_csv_file', self.txt_file_path.text().strip())
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip())
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_crs', self.crs)

    def restore_settings(self):
        settings = QSettings()
        point_type = settings.value('Asistente-LADM-COL/wizards/points_add_points_type') or 'boundary_point'
        if point_type == 'boundary_point':
            self.rad_boundary_point.setChecked(True)
        elif point_type == 'survey_point':
            self.rad_survey_point.setChecked(True)
        else: # 'control_point'
            self.rad_control_point.setChecked(True)

        load_data_type = settings.value('Asistente-LADM-COL/wizards/points_load_data_type') or 'csv'
        if load_data_type == 'refactor':
            self.rad_refactor.setChecked(True)
        else:
            self.rad_csv.setChecked(True)

        self.txt_file_path.setText(settings.value('Asistente-LADM-COL/wizards/points_add_points_csv_file'))
        self.txt_delimiter.setText(settings.value('Asistente-LADM-COL/wizards/points_csv_file_delimiter'))

        self.crs = settings.value('Asistente-LADM-COL/wizards/points_csv_crs', DEFAULT_SRS_AUTHID, str)
        self.update_crs_info()

    def show_help(self):
        show_plugin_help("create_points")
Exemple #3
0
class RightOfWay(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.app = AppInterface()

        self._right_of_way_line_layer = None
        self.addedFeatures = None

    def fill_right_of_way_relations(self, db):
        layers = {
            db.names.LC_ADMINISTRATIVE_SOURCE_T: None,
            db.names.LC_PARCEL_T: None,
            db.names.LC_PLOT_T: None,
            db.names.LC_RESTRICTION_T: None,
            db.names.LC_RESTRICTION_TYPE_D: None,
            db.names.LC_RIGHT_OF_WAY_T: None,
            db.names.COL_RRR_SOURCE_T: None,
            db.names.LC_SURVEY_POINT_T: None,
            db.names.COL_UE_BAUNIT_T: None
        }

        # Load layers
        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        exp = "\"{}\" = '{}'".format(
            db.names.ILICODE_F,
            LADMNames.RESTRICTION_TYPE_D_RIGHT_OF_WAY_ILICODE_VALUE)
        restriction_right_of_way_t_id = [
            feature for feature in layers[
                db.names.LC_RESTRICTION_TYPE_D].getFeatures(exp)
        ][0][db.names.T_ID_F]

        if layers[db.names.LC_PLOT_T].selectedFeatureCount() == 0 or layers[
                db.names.LC_RIGHT_OF_WAY_T].selectedFeatureCount(
                ) == 0 or layers[
                    db.names.LC_ADMINISTRATIVE_SOURCE_T].selectedFeatureCount(
                    ) == 0:
            if self.app.core.get_ladm_layer_from_qgis(
                    db, db.names.LC_PLOT_T,
                    EnumLayerRegistryType.IN_LAYER_TREE) is None:
                self.logger.message_with_button_load_layer_emitted.emit(
                    QCoreApplication.translate(
                        "RightOfWay",
                        "First load the layer {} into QGIS and select at least one plot!"
                    ).format(db.names.LC_PLOT_T),
                    QCoreApplication.translate("RightOfWay",
                                               "Load layer {} now").format(
                                                   db.names.LC_PLOT_T),
                    db.names.LC_PLOT_T, Qgis.Warning)
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "Select at least one benefited plot, one right of way and at least one administrative source to create relations!"
                    ))
                return
        else:
            ue_baunit_features = layers[db.names.COL_UE_BAUNIT_T].getFeatures()
            # Get unique pairs id_right_of_way-id_parcel
            existing_pairs = [
                (ue_baunit_feature[db.names.COL_UE_BAUNIT_T_PARCEL_F],
                 ue_baunit_feature[db.names.COL_UE_BAUNIT_T_LC_RIGHT_OF_WAY_F])
                for ue_baunit_feature in ue_baunit_features
            ]
            existing_pairs = set(existing_pairs)

            plot_ids = [
                f[db.names.T_ID_F]
                for f in layers[db.names.LC_PLOT_T].selectedFeatures()
            ]

            right_of_way_id = layers[
                db.names.LC_RIGHT_OF_WAY_T].selectedFeatures()[0].attribute(
                    db.names.T_ID_F)
            id_pairs = list()
            for plot in plot_ids:
                exp = "\"{uebaunit}\" = {plot}".format(
                    uebaunit=db.names.COL_UE_BAUNIT_T_LC_PLOT_F, plot=plot)
                parcels = layers[db.names.COL_UE_BAUNIT_T].getFeatures(exp)
                for parcel in parcels:
                    id_pair = (parcel.attribute(
                        db.names.COL_UE_BAUNIT_T_PARCEL_F), right_of_way_id)
                    id_pairs.append(id_pair)

            if len(id_pairs) < len(plot_ids):
                # If any relationship plot-parcel is not found, we don't need to continue
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "One or more pairs id_plot-id_parcel weren't found, this is needed to create benefited and restriction relations."
                    ))
                return

            if id_pairs:
                new_features = list()
                for id_pair in id_pairs:
                    if not id_pair in existing_pairs:
                        #Create feature
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.COL_UE_BAUNIT_T])
                        new_feature.setAttribute(
                            db.names.COL_UE_BAUNIT_T_PARCEL_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_UE_BAUNIT_T_LC_RIGHT_OF_WAY_F,
                            id_pair[1])
                        self.logger.info(
                            __name__, "Saving RightOfWay-Parcel: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_features.append(new_feature)

                layers[db.names.COL_UE_BAUNIT_T].dataProvider().addFeatures(
                    new_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(len(new_features), len(id_pairs),
                             db.names.COL_UE_BAUNIT_T,
                             len(id_pairs) - len(new_features), len(id_pairs)))

            spatial_join_layer = processing.run(
                "qgis:joinattributesbylocation", {
                    'INPUT':
                    layers[db.names.LC_PLOT_T],
                    'JOIN':
                    QgsProcessingFeatureSourceDefinition(
                        layers[db.names.LC_RIGHT_OF_WAY_T].id(), True),
                    'PREDICATE': [0],
                    'JOIN_FIELDS': [db.names.T_ID_F],
                    'METHOD':
                    0,
                    'DISCARD_NONMATCHING':
                    True,
                    'PREFIX':
                    '',
                    'OUTPUT':
                    'memory:'
                })['OUTPUT']

            restriction_features = layers[
                db.names.LC_RESTRICTION_T].getFeatures()
            existing_restriction_pairs = [
                (restriction_feature[db.names.COL_BAUNIT_RRR_T_UNIT_F],
                 restriction_feature[db.names.COL_RRR_T_DESCRIPTION_F])
                for restriction_feature in restriction_features
            ]
            existing_restriction_pairs = set(existing_restriction_pairs)
            id_pairs_restriction = list()
            plot_ids = spatial_join_layer.getFeatures()

            for plot in plot_ids:
                exp = "\"uebaunit\" = {plot}".format(
                    uebaunit=db.names.COL_UE_BAUNIT_T_LC_PLOT_F,
                    plot=plot.attribute(db.names.T_ID_F))
                parcels = layers[db.names.COL_UE_BAUNIT_T].getFeatures(exp)
                for parcel in parcels:
                    id_pair_restriction = (parcel.attribute(
                        db.names.COL_UE_BAUNIT_T_PARCEL_F),
                                           QCoreApplication.translate(
                                               "RightOfWay", "Right of way"))
                    id_pairs_restriction.append(id_pair_restriction)

            new_restriction_features = list()
            if id_pairs_restriction:
                for id_pair in id_pairs_restriction:
                    if not id_pair in existing_restriction_pairs:
                        #Create feature
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.LC_RESTRICTION_T])
                        new_feature.setAttribute(
                            db.names.COL_BAUNIT_RRR_T_UNIT_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_RRR_T_DESCRIPTION_F, id_pair[1])
                        new_feature.setAttribute(
                            db.names.LC_RESTRICTION_T_TYPE_F,
                            restriction_right_of_way_t_id)
                        self.logger.info(
                            __name__, "Saving RightOfWay-Parcel: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_restriction_features.append(new_feature)

                layers[db.names.LC_RESTRICTION_T].dataProvider().addFeatures(
                    new_restriction_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(
                        len(new_restriction_features),
                        len(id_pairs_restriction), db.names.LC_RESTRICTION_T,
                        len(id_pairs_restriction) -
                        len(new_restriction_features),
                        len(id_pairs_restriction)))

            administrative_source_ids = [
                f[db.names.T_ID_F] for f in layers[
                    db.names.LC_ADMINISTRATIVE_SOURCE_T].selectedFeatures()
            ]

            source_relation_features = layers[
                db.names.COL_RRR_SOURCE_T].getFeatures()

            existing_source_pairs = [
                (source_relation_feature[db.names.COL_RRR_SOURCE_T_SOURCE_F],
                 source_relation_feature[
                     db.names.COL_RRR_SOURCE_T_LC_RESTRICTION_F])
                for source_relation_feature in source_relation_features
            ]
            existing_source_pairs = set(existing_source_pairs)

            rrr_source_relation_pairs = list()

            for administrative_source_id in administrative_source_ids:
                for restriction_feature in new_restriction_features:
                    rrr_source_relation_pair = (administrative_source_id,
                                                restriction_feature.attribute(
                                                    db.names.T_ID_F))
                    rrr_source_relation_pairs.append(rrr_source_relation_pair)

            new_rrr_source_relation_features = list()
            if rrr_source_relation_pairs:
                for id_pair in rrr_source_relation_pairs:
                    if not id_pair in existing_source_pairs:
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.COL_RRR_SOURCE_T])
                        new_feature.setAttribute(
                            db.names.COL_RRR_SOURCE_T_SOURCE_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_RRR_SOURCE_T_LC_RESTRICTION_F,
                            id_pair[1])
                        self.logger.info(
                            __name__,
                            "Saving Restriction-Source: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_rrr_source_relation_features.append(new_feature)

                layers[db.names.COL_RRR_SOURCE_T].dataProvider().addFeatures(
                    new_rrr_source_relation_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(
                        len(new_rrr_source_relation_features),
                        len(rrr_source_relation_pairs),
                        db.names.COL_RRR_SOURCE_T,
                        len(rrr_source_relation_pairs) -
                        len(new_rrr_source_relation_features),
                        len(rrr_source_relation_pairs)))
Exemple #4
0
class STTaskManager(QObject):
    """
    Retrieve tasks for a user from the Transitional System's Task Service and store them during the session.
    """
    task_started = pyqtSignal(int)  # task_id
    task_canceled = pyqtSignal(int)  # task_id
    task_closed = pyqtSignal(int)  # task_id

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.__registered_tasks = dict()
        self.st_config = TransitionalSystemConfig()

    @_with_override_cursor
    def __retrieve_tasks(self, st_user, task_type=None, task_status=None):
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
            # 'User-Agent': "PostmanRuntime/7.20.1",
            'Accept': "*/*",
            'Cache-Control': "no-cache",
            # 'Postman-Token': "987c7fbf-af4d-42e8-adee-687f35f4a4a0,0547120a-6f8e-42a8-b97f-f052602cc7ff",
            # 'Host': "st.local:8090",
            'Accept-Encoding': "gzip, deflate",
            'Connection': "keep-alive",
            'cache-control': "no-cache"
        }

        try:
            self.logger.debug(__name__, "Retrieving tasks from server...")
            response = requests.request("GET", self.st_config.ST_GET_TASKS_SERVICE_URL, headers=headers)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # Parse, create and register tasks
            response_data = json.loads(response.text)
            for task_data in response_data:
                task = STTask(task_data)
                if task.is_valid():
                    self.__register_task(task)
        else:
             if response.status_code == 500:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
             elif response.status_code > 500 and response.status_code < 600:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
             elif response.status_code == 401:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)

    def get_tasks(self, st_user, task_type=None, task_status=None):
        """
        Go to server for current tasks per user
        :param st_user:
        :param task_type: To filter task types. Still unused.
        :param task_status: To filter task statuses. Still unused.
        :return: dict of task ids with the corresponding task object
        """
        # Each call refreshes the registered tasks.
        self.unregister_tasks()
        self.__retrieve_tasks(st_user, task_type, task_status)

        return self.__registered_tasks

    def get_task(self, task_id):
        task = self.__registered_tasks[task_id] if task_id in self.__registered_tasks else None
        if task is None:
            self.logger.warning(__name__, "Task {} not found!!!".format(task_id))
        else:
            self.logger.info(__name__, "Task {} found!!!".format(task_id))
        return task

    def __register_task(self, task):
        self.logger.debug(__name__, "Task {} registered!".format(task.get_id()))
        self.__registered_tasks[task.get_id()] = task

    def __unregister_task(self, task_id):
        self.logger.debug(__name__, "Task {} unregistered!".format(task_id))
        self.__registered_tasks[task_id] = None
        del self.__registered_tasks[task_id]

    def unregister_tasks(self):
        for k,v in self.__registered_tasks.items():
            self.__registered_tasks[k] = None

        self.__registered_tasks = dict()
        self.logger.info(__name__, "All tasks have been unregistered!")

    @_with_override_cursor
    def start_task(self, st_user, task_id):
        payload = {}
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
        }

        try:
            self.logger.debug(__name__, "Telling the server to start a task...")
            response = requests.request("PUT", self.st_config.ST_START_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # Parse response
            response_data = json.loads(response.text)
            self.logger.info(__name__, "Task id '{}' started in server!...".format(task_id))
            self.logger.info_msg(__name__, QCoreApplication.translate("TaskManager",
                                                                      "The task '{}' was successfully started!".format(
                                                                          self.get_task(task_id).get_name())))
            self.update_task_info(task_id, response_data)
            self.task_started.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            else:
                self.logger.warning(__name__, "Status code not handled: {}".format(response.status_code))

    @_with_override_cursor
    def cancel_task(self, st_user, task_id, reason):
        payload = json.dumps({"reason": reason})
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
            'Content-Type': 'application/json'
        }

        try:
            self.logger.debug(__name__, "Telling the server to cancel a task...")
            response = requests.request("PUT", self.st_config.ST_CANCEL_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # No need to parse response this time, we'll ask tasks from server again anyways
            self.logger.info(__name__, "Task id '{}' canceled in server!".format(task_id))
            self.logger.info_msg(__name__, QCoreApplication.translate("TaskManager", "The task '{}' was successfully canceled!".format(self.get_task(task_id).get_name())))
            self.task_canceled.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            else:
                self.logger.warning(__name__, "Status code not handled: {}, payload: {}".format(response.status_code, payload))

    @_with_override_cursor
    def close_task(self, st_user, task_id):
        payload = {}
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
        }

        try:
            self.logger.debug(__name__, "Telling the server to close a task...")
            response = requests.request("PUT", self.st_config.ST_CLOSE_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # No need to parse response this time, we'll ask tasks from server again anyways
            self.logger.success(__name__, "Task id '{}' closed in server!".format(task_id))
            self.logger.success_msg(__name__, QCoreApplication.translate("TaskManager",
                                                                      "The task '{}' was successfully closed!".format(
                                                                          self.get_task(task_id).get_name())))
            self.task_closed.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            elif response.status_code == 422:
                response_data = json.loads(response.text)
                msg = QCoreApplication.translate("STSession", QCoreApplication.translate("TaskManager",
                    "Task not closed! Details: {}").format(response_data['message'] if 'message' in response_data else "Unreadable response from server."))
                self.logger.warning_msg(__name__, msg)
            else:
                self.logger.warning(__name__, "Status code not handled: {}".format(response.status_code))

    def update_task_info(self, task_id, task_data):
        task = STTask(task_data)
        if task.is_valid():
            self.__unregister_task(task_id)
            self.__register_task(task)
class SourceHandler(QObject):
    """
    Upload source files from a given field of a layer to a remote server that
    is configured in Settings Dialog. The server returns a file URL that is
    then stored in the source table.
    """
    def __init__(self, qgis_utils):
        QObject.__init__(self)
        self.qgis_utils = qgis_utils
        self.logger = Logger()

    def upload_files(self, layer, field_index, features):
        """
        Upload given features' source files to remote server and return a dict
        formatted as changeAttributeValues expects to update 'datos' attribute
        to a remote location.
        """
        if not QSettings().value(
                'Asistente-LADM_COL/sources/document_repository', False, bool):
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "SourceHandler",
                    "The source files were not uploaded to the document repository because you have that option unchecked. You can still upload the source files later using the 'Upload Pending Source Files' menu."
                ), 10)
            return dict()

        # Test if we have Internet connection and a valid service
        res, msg = self.qgis_utils.is_source_service_valid(
        )  # TODO: Bring this method from qgis_utils

        if not res:
            msg['text'] = QCoreApplication.translate(
                "SourceHandler",
                "No file could be uploaded to the document repository. You can do it later from the 'Upload Pending Source Files' menu. Reason: {}"
            ).format(msg['text'])
            self.logger.info_msg(
                __name__, msg['text'],
                20)  # The data is still saved, so always show Info msg
            return dict()

        file_features = [
            feature for feature in features if not feature[field_index] == NULL
            and os.path.isfile(feature[field_index])
        ]
        total = len(features)
        not_found = total - len(file_features)

        upload_dialog = UploadProgressDialog(len(file_features), not_found)
        upload_dialog.show()
        count = 0
        upload_errors = 0
        new_values = dict()

        for feature in file_features:
            data_url = feature[field_index]
            file_name = os.path.basename(data_url)

            nam = QNetworkAccessManager()
            #reply.downloadProgress.connect(upload_dialog.update_current_progress)

            multiPart = QHttpMultiPart(QHttpMultiPart.FormDataType)
            textPart = QHttpPart()
            textPart.setHeader(QNetworkRequest.ContentDispositionHeader,
                               QVariant("form-data; name=\"driver\""))
            textPart.setBody(QByteArray().append('Local'))

            filePart = QHttpPart()
            filePart.setHeader(
                QNetworkRequest.ContentDispositionHeader,
                QVariant("form-data; name=\"file\"; filename=\"{}\"".format(
                    file_name)))
            file = QFile(data_url)
            file.open(QIODevice.ReadOnly)

            filePart.setBodyDevice(file)
            file.setParent(
                multiPart
            )  # we cannot delete the file now, so delete it with the multiPart

            multiPart.append(filePart)
            multiPart.append(textPart)

            service_url = '/'.join([
                QSettings().value(
                    'Asistente-LADM_COL/sources/service_endpoint',
                    DEFAULT_ENDPOINT_SOURCE_SERVICE),
                SOURCE_SERVICE_UPLOAD_SUFFIX
            ])
            request = QNetworkRequest(QUrl(service_url))
            reply = nam.post(request, multiPart)
            #reply.uploadProgress.connect(upload_dialog.update_current_progress)
            reply.error.connect(self.error_returned)
            multiPart.setParent(reply)

            # We'll block execution until we get response from the server
            loop = QEventLoop()
            reply.finished.connect(loop.quit)
            loop.exec_()

            response = reply.readAll()
            data = QTextStream(response, QIODevice.ReadOnly)
            content = data.readAll()

            if content is None:
                self.logger.critical(
                    __name__,
                    "There was an error uploading file '{}'".format(data_url))
                upload_errors += 1
                continue

            try:
                response = json.loads(content)
            except json.decoder.JSONDecodeError:
                self.logger.critical(
                    __name__,
                    "Couldn't parse JSON response from server for file '{}'!!!"
                    .format(data_url))
                upload_errors += 1
                continue

            if 'error' in response:
                self.logger.critical(
                    __name__,
                    "STATUS: {}. ERROR: {} MESSAGE: {} FILE: {}".format(
                        response['status'], response['error'],
                        response['message'], data_url))
                upload_errors += 1
                continue

            reply.deleteLater()

            if 'url' not in response:
                self.logger.critical(
                    __name__,
                    "'url' attribute not found in JSON response for file '{}'!"
                    .format(data_url))
                upload_errors += 1
                continue

            url = self.get_file_url(response['url'])
            new_values[feature.id()] = {field_index: url}

            count += 1
            upload_dialog.update_total_progress(count)

        if not_found > 0:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "SourceHandler",
                    "{} out of {} records {} not uploaded to the document repository because {} file path is NULL or it couldn't be found in the local disk!"
                ).format(
                    not_found, total,
                    QCoreApplication.translate("SourceHandler", "was")
                    if not_found == 1 else QCoreApplication.translate(
                        "SourceHandler", "were"),
                    QCoreApplication.translate("SourceHandler", "its")
                    if not_found == 1 else QCoreApplication.translate(
                        "SourceHandler", "their")))
        if len(new_values):
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "SourceHandler",
                    "{} out of {} files {} uploaded to the document repository and {} remote location stored in the database!"
                ).format(
                    len(new_values), total,
                    QCoreApplication.translate("SourceHandler", "was")
                    if len(new_values) == 1 else QCoreApplication.translate(
                        "SourceHandler", "were"),
                    QCoreApplication.translate("SourceHandler", "its")
                    if len(new_values) == 1 else QCoreApplication.translate(
                        "SourceHandler", "their")))
        if upload_errors:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "SourceHandler",
                    "{} out of {} files could not be uploaded to the document repository because of upload errors! See log for details."
                ).format(upload_errors, total))

        return new_values

    def error_returned(self, error_code):
        self.logger.critical(__name__,
                             "Qt network error code: {}".format(error_code))

    def handle_source_upload(self, db, layer, field_name):

        layer_name = db.get_ladm_layer_name(layer)
        field_index = layer.fields().indexFromName(field_name)

        def features_added(layer_id, features):
            modified_layer = QgsProject.instance().mapLayer(layer_id)

            if modified_layer is None:
                return

            modified_layer_name = db.get_ladm_layer_name(modified_layer,
                                                         validate_is_ladm=True)
            if modified_layer_name is None:
                return

            if modified_layer_name.lower() != layer_name.lower():
                return

            with OverrideCursor(Qt.WaitCursor):
                new_values = self.upload_files(modified_layer, field_index,
                                               features)

            if new_values:
                modified_layer.dataProvider().changeAttributeValues(new_values)

        layer.committedFeaturesAdded.connect(features_added)

    def get_file_url(self, part):
        endpoint = QSettings().value(
            'Asistente-LADM_COL/sources/service_endpoint',
            DEFAULT_ENDPOINT_SOURCE_SERVICE)
        return '/'.join([endpoint, part[1:] if part.startswith('/') else part])
class ReportGenerator(QObject):
    LOG_TAB = 'LADM-COL Reports'

    enable_action_requested = pyqtSignal(str, bool)

    def __init__(self, ladm_data):
        QObject.__init__(self)
        self.ladm_data = ladm_data
        self.logger = Logger()
        self.app = AppInterface()
        self.java_dependency = JavaDependency()
        self.java_dependency.download_dependency_completed.connect(
            self.download_java_complete)

        self.report_dependency = ReportDependency()
        self.report_dependency.download_dependency_completed.connect(
            self.download_report_complete)

        self.encoding = locale.getlocale()[1]
        # This might be unset
        if not self.encoding:
            self.encoding = 'UTF8'

        self._downloading = False

    def stderr_ready(self, proc):
        text = bytes(proc.readAllStandardError()).decode(self.encoding)
        self.logger.critical(__name__, text, tab=self.LOG_TAB)

    def stdout_ready(self, proc):
        text = bytes(proc.readAllStandardOutput()).decode(self.encoding)
        self.logger.info(__name__, text, tab=self.LOG_TAB)

    def update_yaml_config(self, db, config_path):
        text = ''
        qgs_uri = QgsDataSourceUri(db.uri)

        with open(os.path.join(config_path, 'config_template.yaml')) as f:
            text = f.read()
            text = text.format('{}',
                               DB_USER=qgs_uri.username(),
                               DB_PASSWORD=qgs_uri.password(),
                               DB_HOST=qgs_uri.host(),
                               DB_PORT=qgs_uri.port(),
                               DB_NAME=qgs_uri.database())
        new_file_path = os.path.join(
            config_path, self.get_tmp_filename('yaml_config', 'yaml'))

        with open(new_file_path, 'w') as new_yaml:
            new_yaml.write(text)

        return new_file_path

    def get_layer_geojson(self, db, layer_name, plot_id, report_type):
        if report_type == ANNEX_17_REPORT:
            if layer_name == 'terreno':
                return db.get_annex17_plot_data(plot_id, 'only_id')
            elif layer_name == 'terrenos':
                return db.get_annex17_plot_data(plot_id, 'all_but_id')
            elif layer_name == 'terrenos_all':
                return db.get_annex17_plot_data(plot_id, 'all')
            elif layer_name == 'construcciones':
                return db.get_annex17_building_data()
            else:
                return db.get_annex17_point_data(plot_id)
        else:  #report_type == ANT_MAP_REPORT:
            if layer_name == 'terreno':
                return db.get_ant_map_plot_data(plot_id, 'only_id')
            elif layer_name == 'terrenos':
                return db.get_ant_map_plot_data(plot_id, 'all_but_id')
            elif layer_name == 'terrenos_all':
                return db.get_annex17_plot_data(plot_id, 'all')
            elif layer_name == 'construcciones':
                return db.get_annex17_building_data()
            elif layer_name == 'puntoLindero':
                return db.get_annex17_point_data(plot_id)
            else:  #layer_name == 'cambio_colindancia':
                return db.get_ant_map_neighbouring_change_data(plot_id)

    def update_json_data(self, db, json_spec_file, plot_id, tmp_dir,
                         report_type):
        json_data = dict()
        with open(json_spec_file) as f:
            json_data = json.load(f)

        json_data['attributes']['id'] = plot_id
        json_data['attributes']['datasetName'] = db.schema
        layers = json_data['attributes']['map']['layers']
        for layer in layers:
            layer['geoJson'] = self.get_layer_geojson(db, layer['name'],
                                                      plot_id, report_type)

        overview_layers = json_data['attributes']['overviewMap']['layers']
        for layer in overview_layers:
            layer['geoJson'] = self.get_layer_geojson(db, layer['name'],
                                                      plot_id, report_type)

        new_json_file_path = os.path.join(
            tmp_dir,
            self.get_tmp_filename('json_data_{}'.format(plot_id), 'json'))
        with open(new_json_file_path, 'w') as new_json:
            new_json.write(json.dumps(json_data))

        return new_json_file_path

    def get_tmp_dir(self, create_random=True):
        if create_random:
            return tempfile.mkdtemp()

        return tempfile.gettempdir()

    def get_tmp_filename(self, basename, extension='gpkg'):
        return "{}_{}.{}".format(basename,
                                 str(time.time()).replace(".", ""), extension)

    def generate_report(self, db, report_type):
        # Check if mapfish and Jasper are installed, otherwise show where to
        # download them from and return
        if not self.report_dependency.check_if_dependency_is_valid():
            self.report_dependency.download_dependency(URL_REPORTS_LIBRARIES)
            return

        java_home_set = self.java_dependency.set_java_home()
        if not java_home_set:
            self.java_dependency.get_java_on_demand()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "Java is a prerequisite. Since it was not found, it is being configured..."
                ))
            return

        plot_layer = self.app.core.get_layer(db, db.names.LC_PLOT_T, load=True)
        if not plot_layer:
            return

        selected_plots = plot_layer.selectedFeatures()
        if not selected_plots:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "To generate reports, first select at least a plot!"))
            return

        # Where to store the reports?
        previous_folder = QSettings().value(
            "Asistente-LADM-COL/reports/save_into_dir", ".")
        save_into_folder = QFileDialog.getExistingDirectory(
            None,
            QCoreApplication.translate(
                "ReportGenerator",
                "Select a folder to save the reports to be generated"),
            previous_folder)
        if not save_into_folder:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You need to select a folder where to save the reports before continuing."
                ))
            return
        QSettings().setValue("Asistente-LADM-COL/reports/save_into_dir",
                             save_into_folder)

        config_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, report_type)
        json_spec_file = os.path.join(config_path, 'spec_json_file.json')

        script_name = ''
        if os.name == 'posix':
            script_name = 'print'
        elif os.name == 'nt':
            script_name = 'print.bat'

        script_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, 'bin',
                                   script_name)
        if not os.path.isfile(script_path):
            self.logger.warning(
                __name__,
                "Script file for reports wasn't found! {}".format(script_path))
            return

        self.enable_action_requested.emit(report_type, False)

        # Update config file
        yaml_config_path = self.update_yaml_config(db, config_path)
        self.logger.debug(
            __name__, "Config file for reports: {}".format(yaml_config_path))

        total = len(selected_plots)
        step = 0
        count = 0
        tmp_dir = self.get_tmp_dir()

        # Progress bar setup
        progress = QProgressBar()
        if total == 1:
            progress.setRange(0, 0)
        else:
            progress.setRange(0, 100)
        progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.app.gui.create_progress_message_bar(
            QCoreApplication.translate("ReportGenerator",
                                       "Generating {} report{}...").format(
                                           total, '' if total == 1 else 's'),
            progress)

        polygons_with_holes = []
        multi_polygons = []

        for selected_plot in selected_plots:
            plot_id = selected_plot[db.names.T_ID_F]

            geometry = selected_plot.geometry()
            abstract_geometry = geometry.get()
            if abstract_geometry.ringCount() > 1:
                polygons_with_holes.append(str(plot_id))
                self.logger.warning(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Skipping Annex 17 for plot with {}={} because it has holes. The reporter module does not support such polygons."
                    ).format(db.names.T_ID_F, plot_id))
                continue
            if abstract_geometry.numGeometries() > 1:
                multi_polygons.append(str(plot_id))
                self.logger.warning(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Skipping Annex 17 for plot with {}={} because it is a multi-polygon. The reporter module does not support such polygons."
                    ).format(db.names.T_ID_F, plot_id))
                continue

            # Generate data file
            json_file = self.update_json_data(db, json_spec_file, plot_id,
                                              tmp_dir, report_type)
            self.logger.debug(__name__,
                              "JSON file for reports: {}".format(json_file))

            # Run sh/bat passing config and data files
            proc = QProcess()
            proc.readyReadStandardError.connect(
                functools.partial(self.stderr_ready, proc=proc))
            proc.readyReadStandardOutput.connect(
                functools.partial(self.stdout_ready, proc=proc))

            parcel_number = self.ladm_data.get_parcels_related_to_plots(
                db, [plot_id], db.names.LC_PARCEL_T_PARCEL_NUMBER_F) or ['']
            file_name = '{}_{}_{}.pdf'.format(report_type, plot_id,
                                              parcel_number[0])

            current_report_path = os.path.join(save_into_folder, file_name)
            proc.start(script_path, [
                '-config', yaml_config_path, '-spec', json_file, '-output',
                current_report_path
            ])

            if not proc.waitForStarted():
                # Grant execution permissions
                os.chmod(
                    script_path, stat.S_IXOTH | stat.S_IXGRP | stat.S_IXUSR
                    | stat.S_IRUSR | stat.S_IRGRP)
                proc.start(script_path, [
                    '-config', yaml_config_path, '-spec', json_file, '-output',
                    current_report_path
                ])

            if not proc.waitForStarted():
                proc = None
                self.logger.warning(
                    __name__, "Couldn't execute script to generate report...")
            else:
                loop = QEventLoop()
                proc.finished.connect(loop.exit)
                loop.exec()

                self.logger.debug(__name__,
                                  "{}:{}".format(plot_id, proc.exitCode()))
                if proc.exitCode() == 0:
                    count += 1

                step += 1
                progress.setValue(step * 100 / total)

        os.remove(yaml_config_path)

        self.enable_action_requested.emit(report_type, True)
        self.logger.clear_message_bar()

        if total == count:
            if total == 1:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "The report <a href='file:///{}'>{}</a> was successfully generated!"
                ).format(normalize_local_url(save_into_folder), file_name)
            else:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "All reports were successfully generated in folder <a href='file:///{path}'>{path}</a>!"
                ).format(path=normalize_local_url(save_into_folder))

            self.logger.success_msg(__name__, msg)
        else:
            details_msg = ''
            if polygons_with_holes:
                details_msg += QCoreApplication.translate(
                    "ReportGenerator",
                    " The following polygons were skipped because they have holes and are not supported: {}."
                ).format(", ".join(polygons_with_holes))
            if multi_polygons:
                details_msg += QCoreApplication.translate(
                    "ReportGenerator",
                    " The following polygons were skipped because they are multi-polygons and are not supported: {}."
                ).format(", ".join(multi_polygons))

            if total == 1:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "The report for plot {} couldn't be generated!{} See QGIS log (tab '{}') for details."
                ).format(plot_id, details_msg, self.LOG_TAB)
            else:
                if count == 0:
                    msg = QCoreApplication.translate(
                        "ReportGenerator",
                        "No report could be generated!{} See QGIS log (tab '{}') for details."
                    ).format(details_msg, self.LOG_TAB)
                else:
                    msg = QCoreApplication.translate(
                        "ReportGenerator",
                        "At least one report couldn't be generated!{details_msg} See QGIS log (tab '{log_tab}') for details. Go to <a href='file:///{path}'>{path}</a> to see the reports that were generated."
                    ).format(details_msg=details_msg,
                             path=normalize_local_url(save_into_folder),
                             log_tab=self.LOG_TAB)

            self.logger.warning_msg(__name__, msg)

    def download_java_complete(self):
        if self.java_dependency.fetcher_task and not self.java_dependency.fetcher_task.isCanceled(
        ):
            if self.java_dependency.check_if_dependency_is_valid():
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Java was successfully configured!"), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You have just canceled the Java dependency download."), 5)

    def download_report_complete(self):
        if self.report_dependency.fetcher_task and not self.report_dependency.fetcher_task.isCanceled(
        ):
            if self.report_dependency.check_if_dependency_is_valid():
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Report dependency was successfully configured!"), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You have just canceled the report dependency download."),
                5)
Exemple #7
0
class BaseDockWidgetFieldDataCapture(QgsDockWidget, DOCKWIDGET_UI):
    def __init__(self, iface, db, ladm_data, allocate_mode=True):
        super(BaseDockWidgetFieldDataCapture, self).__init__(None)
        self.setupUi(self)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.logger = Logger()
        self.logger.clear_message_bar()  # Clear QGIS message bar

        self._controller = self._get_controller(iface, db, ladm_data)
        self._controller.field_data_capture_layer_removed.connect(self.layer_removed)

        # Configure panels
        self.configure_receivers_panel = None
        self.lst_configure_receivers_panel = list()

        self.allocate_parcels_to_receiver_panel = None
        self.lst_allocate_parcels_to_receiver_panel = list()

        self.split_data_for_receivers_panel = None
        self.lst_split_data_for_receivers_panel = list()

        self.allocate_panel = None

        if allocate_mode:
            self._initialize_allocate_initial_panel()
        else:  # Synchronize mode
            # self.synchronize_panel = ChangesPerParcelPanelWidget(self, self.utils)
            # self.widget.setMainPanel(self.synchronize_panel)
            # self.lst_parcel_panels.append(self.synchronize_panel)
            self._initialize_synchronize_initial_panel()

    def _get_controller(self, iface, db, ladm_data):
        raise NotImplementedError

    def _initialize_allocate_initial_panel(self):
        raise NotImplementedError

    def _initialize_synchronize_initial_panel(self):
        raise NotImplementedError

    def show_configure_receivers_panel(self):
        with OverrideCursor(Qt.WaitCursor):
            self.__reset_receivers_panel_vars()

            self.configure_receivers_panel = self._get_receivers_panel()
            self.configure_receivers_panel.clear_message_bar_requested.connect(
                self.allocate_panel.panel_accepted_clear_message_bar)
            self.widget.showPanel(self.configure_receivers_panel)
            self.lst_configure_receivers_panel.append(self.configure_receivers_panel)

    def __reset_receivers_panel_vars(self):
        if self.lst_configure_receivers_panel:
            for panel in self.lst_configure_receivers_panel:
                try:
                    self.widget.closePanel(panel)
                except RuntimeError as e:  # Panel in C++ could be already closed...
                    pass

            self.lst_configure_receivers_panel = list()
            self.configure_receivers_panel = None

    def _get_receivers_panel(self):
        raise NotImplementedError

    def show_allocate_parcels_to_receiver_panel(self, selected_parcels):
        with OverrideCursor(Qt.WaitCursor):
            self._reset_allocate_parcels_to_receiver_panel_vars()

            self.allocate_parcels_to_receiver_panel = self._get_allocate_to_receiver_panel(selected_parcels)
            self.allocate_parcels_to_receiver_panel.refresh_parcel_data_requested.connect(
                self.allocate_panel.panel_accepted_refresh_parcel_data)
            self.widget.showPanel(self.allocate_parcels_to_receiver_panel)
            self.lst_allocate_parcels_to_receiver_panel.append(self.allocate_parcels_to_receiver_panel)

    def _reset_allocate_parcels_to_receiver_panel_vars(self):
        if self.lst_allocate_parcels_to_receiver_panel:
            for panel in self.lst_allocate_parcels_to_receiver_panel:
                try:
                    self.widget.closePanel(panel)
                except RuntimeError as e:  # Panel in C++ could be already closed...
                    pass

            self.lst_allocate_parcels_to_receiver_panel = list()
            self.allocate_parcels_to_receiver_panel = None

    def _get_allocate_to_receiver_panel(self, selected_parcels):
        raise NotImplementedError

    def show_split_data_for_receivers_panel(self):
        with OverrideCursor(Qt.WaitCursor):
            self._reset_split_data_for_receivers_panel_vars()

            self.split_data_for_receivers_panel = self._get_split_data_for_receivers_panel()
            self.split_data_for_receivers_panel.refresh_parcel_data_clear_selection_requested.connect(
                self.allocate_panel.panel_accepted_refresh_and_clear_selection)
            self.widget.showPanel(self.split_data_for_receivers_panel)
            self.lst_split_data_for_receivers_panel.append(self.split_data_for_receivers_panel)

    def _get_split_data_for_receivers_panel(self):
        raise NotImplementedError

    def _reset_split_data_for_receivers_panel_vars(self):
        if self.lst_split_data_for_receivers_panel:
            for panel in self.lst_split_data_for_receivers_panel:
                try:
                    self.widget.closePanel(panel)
                except RuntimeError as e:  # Panel in C++ could be already closed...
                    pass

            self.lst_split_data_for_receivers_panel = list()
            self.split_data_for_receivers_panel = None

    def closeEvent(self, event):
        # Close here open signals in other panels (if needed)
        if self.allocate_panel:
            self.allocate_panel.close_panel()

        self.close_dock_widget()

    def add_layers(self):
        self._controller.add_layers()

    def layer_removed(self):
        self.logger.info_msg(__name__, QCoreApplication.translate("DockWidgetFieldDataCapture",
                                                                  "'Field data capture' has been closed because you just removed a required layer."))
        self.close_dock_widget()

    def update_db_connection(self, db, ladm_col_db, db_source):
        self.close_dock_widget()  # New DB: the user needs to use the menus again, which will start FDC from scratch

    def close_dock_widget(self):
        try:
            self._controller.field_data_capture_layer_removed.disconnect()  # disconnect layer signals
        except:
            pass

        self.close()  # The user needs to use the menus again, which will start everything from scratch

    def initialize_layers(self):
        self._controller.initialize_layers()
class BaseAllocateParcelsInitialPanelWidget(QgsPanelWidget, WIDGET_UI):
    allocate_parcels_to_receiver_panel_requested = pyqtSignal(dict)  # {parcel_fid: parcel_number}
    configure_receivers_panel_requested = pyqtSignal()
    split_data_for_receivers_panel_requested = pyqtSignal()

    STATUS_COL = 1

    def __init__(self, parent, controller):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.parent = parent
        self._controller = controller
        self.logger = Logger()
        self.app = AppInterface()

        self.setDockMode(True)
        self.setPanelTitle(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Allocate parcels"))
        self.parent.setWindowTitle(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Allocate parcels"))

        self.tbl_parcels.resizeColumnsToContents()

        self.txt_search.valueChanged.connect(self.search_value_changed)
        self.tbl_parcels.itemSelectionChanged.connect(self.selection_changed)
        self.btn_allocate.clicked.connect(self.call_allocate_parcels_to_receiver_panel)
        self.btn_configure_receivers.clicked.connect(self.configure_receivers_panel_requested)
        self.btn_show_summary.clicked.connect(self.split_data_for_receivers_panel_requested)
        self.chk_show_only_not_allocated.stateChanged.connect(self.chk_check_state_changed)
        self.btn_reallocate.clicked.connect(self.reallocate_clicked)

        self.connect_to_plot_selection(True)

        self.__parcel_data = dict()  # {parcel_fid: (parcel_number, surveyor_name)}
        self.__selected_items = dict()  # {parcel_fid: parcel_number}

    def _parcel_data(self, refresh_parcel_data=False):
        if not self.__parcel_data or refresh_parcel_data:
            self.__parcel_data = self._controller.get_parcel_receiver_data()

        return self.__parcel_data

    def fill_data(self, refresh_parcel_data=False):
        self.update_selected_items()  # Save selection

        self.tbl_parcels.blockSignals(True)  # We don't want to get itemSelectionChanged here
        self.tbl_parcels.clearContents()
        self.tbl_parcels.blockSignals(False)  # We don't want to get itemSelectionChanged here

        # Build the parcel_data dict taking configuration (search string, chk filter) into account
        parcel_data = self._parcel_data(refresh_parcel_data).copy()
        if self.chk_show_only_not_allocated.isChecked():
            parcel_data = {k:v for k,v in parcel_data.items() if not v[1]}  # v: (parcel_number, surveyor)
        parcel_data = self.filter_data_by_search_string(parcel_data)

        self.tbl_parcels.setRowCount(len(parcel_data))
        self.tbl_parcels.setSortingEnabled(False)

        self.tbl_parcels.blockSignals(True)  # We don't want to get itemSelectionChanged here
        for row, data in enumerate(parcel_data.items()):
            parcel_number, receiver = data[1]
            self.fill_row(data[0], parcel_number, receiver, row)
        self.tbl_parcels.blockSignals(False)  # We don't want to get itemSelectionChanged here

        self.tbl_parcels.setSortingEnabled(True)
        self.tbl_parcels.resizeColumnsToContents()

    def fill_row(self, parcel_fid, parcel_number, receiver, row):
        item = QTableWidgetItem(parcel_number)
        item.setData(Qt.UserRole, parcel_fid)
        self.tbl_parcels.setItem(row, 0, item)

        item2 = QTableWidgetItem(receiver or '')
        if not receiver:
            item2.setBackground(QBrush(NOT_ALLOCATED_PARCEL_COLOR))
        self.tbl_parcels.setItem(row, self.STATUS_COL, item2)

        if parcel_fid in self.__selected_items:
            item.setSelected(True)
            item2.setSelected(True)

    def filter_data_by_search_string(self, parcel_data):
        value = self.txt_search.value().strip()
        if value and len(value) > 1:
            parcel_data = {k:v for k,v in parcel_data.items() if value in v[0]}

        return parcel_data

    def search_value_changed(self, value):
        self.fill_data()

    def chk_check_state_changed(self, state):
        self.fill_data()

    def update_selected_items(self):
        """Update the internal selected_items dict"""
        selected_gui_items = [item.data(Qt.UserRole) for item in self.tbl_parcels.selectedItems()]
        for row in range(self.tbl_parcels.rowCount()):
            item = self.tbl_parcels.item(row, 0)
            fid = item.data(Qt.UserRole)
            if fid in selected_gui_items:
                self.__selected_items[fid] = item.text()
            else:
                if fid in self.__selected_items:
                    # It was selected before, but not anymore
                    del self.__selected_items[fid]

    def selection_changed(self):
        """React upon manual selection in the table widget"""
        self.update_selected_items()

        self.connect_to_plot_selection(False)  # This plot selection should not trigger a table view selection refresh
        self._controller.update_plot_selection(list(self.__selected_items.keys()))
        self.connect_to_plot_selection(True)

    def update_parcel_selection(self, selected, deselected, clear_and_select):
        """React upon a plot selection"""
        self.tbl_parcels.blockSignals(True)  # We don't want to get itemSelectionChanged here
        self.tbl_parcels.clearSelection()  # Reset GUI selection
        self.__selected_items = dict()  # Reset internal selection dict
        parcel_ids = self._controller.get_parcel_numbers_from_selected_plots()

        for parcel_id in parcel_ids:
            if parcel_id in self._parcel_data():
                parcel_number = self._parcel_data()[parcel_id][0]
                items = self.tbl_parcels.findItems(parcel_number, Qt.MatchExactly)
                if items:
                    items[0].setSelected(True)  # Select item in column 0
                    self.tbl_parcels.item(items[0].row(), self.STATUS_COL).setSelected(True)  # Select item in column 1
                else:  # parcel is not currently shown, so select it in internal dict
                    if parcel_id in self._parcel_data():
                        self.__selected_items[parcel_id] = parcel_number

        self.tbl_parcels.blockSignals(False)
        self.update_selected_items()  # Update the internal selection dict

    def connect_to_plot_selection(self, connect):
        if connect:
            self._controller.plot_layer().selectionChanged.connect(self.update_parcel_selection)
        else:
            try:
                self._controller.plot_layer().selectionChanged.disconnect(self.update_parcel_selection)
            except (TypeError, RuntimeError):  # Layer in C++ could be already deleted...
                pass

    def close_panel(self):
        # Disconnect signals
        self.connect_to_plot_selection(False)

    def panel_accepted_clear_message_bar(self):
        self.logger.clear_message_bar()

    def panel_accepted_refresh_parcel_data(self):
        """Slot for refreshing parcel data when it has changed in other panels"""
        self.panel_accepted_clear_message_bar()
        self.fill_data(True)

    def panel_accepted_refresh_and_clear_selection(self):
        self.panel_accepted_refresh_parcel_data()  # Refresh data in table widget, as it might be out of sync with newly added layers
        self.tbl_parcels.clearSelection()  # Selection might be remembered from the status before converting to offline

    def call_allocate_parcels_to_receiver_panel(self):
        # Make sure that all selected items are not yet allocated, otherwise, allow users to deallocate selected
        already_allocated = list()  # [parcel_fid1, ...]
        for parcel_fid, parcel_number in self.__selected_items.items():
            if parcel_fid in self._parcel_data():
                if self._parcel_data()[parcel_fid][1]:  # surveyor_name
                    already_allocated.append(parcel_fid)

        if already_allocated:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Question)
            msg.setText(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                   "Some selected parcels are already allocated!\n\nWhat would you like to do with selected parcels that are already allocated?"))
            msg.setWindowTitle(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Warning"))
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            msg.button(QMessageBox.Yes).setText(
                QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Deselect them and continue"))
            msg.button(QMessageBox.No).setText(
                QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Reallocate and continue"))
            reply = msg.exec_()

            if reply == QMessageBox.Yes:  # Ignore
                # Remove selection of allocated parcels, reload table widget data and continue
                for allocated_parcel_id in already_allocated:
                    if allocated_parcel_id in self._parcel_data():
                        items = self.tbl_parcels.findItems(self._parcel_data()[allocated_parcel_id][0], Qt.MatchExactly)
                        if items:  # Item is currently shown, so deselect it in GUI
                            items[0].setSelected(False)  # Deselect item in column 0
                            self.tbl_parcels.item(items[0].row(), self.STATUS_COL).setSelected(False)  # Deselect item in column 1
                        else:  # Item is not currently shown, deselected in internal selection dict
                            if allocated_parcel_id in self.__selected_items:
                                del self.__selected_items[allocated_parcel_id]

                self.fill_data()

                if not self.__selected_items:
                    self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                       "Ignoring selected parcels, there are none to be allocated! First select some!"), 10)
                    return

            elif reply == QMessageBox.No:  # Reallocate
                # Preserve the selected_items dict, but remove allocation before continuing
                if not self.discard_parcel_allocation(already_allocated):
                    return
            else:  # QMessageBox.Cancel
                return

        if self.__selected_items:
            self.allocate_parcels_to_receiver_panel_requested.emit(self.__selected_items)
        else:
            self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "First select some parcels to be allocated."), 5)

    def discard_parcel_allocation(self, parcel_fids):
        res = self._controller.discard_parcel_allocation(parcel_fids)
        if res:
            self.fill_data(True)  # Refresh parcel data
            self.logger.success_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                         "Selected parcels are now not allocated!"))
        else:
            self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                         "There were troubles reallocating parcels!"))
        return res

    def reallocate_clicked(self):
        if not self.__selected_items:
            self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                         "First select some parcels."), 5)
            return

        # Get selected parcels that are already allocated
        already_allocated = list()  # [parcel_fid1, ...]
        for parcel_fid, parcel_number in self.__selected_items.items():
            if parcel_fid in self._parcel_data():
                if self._parcel_data()[parcel_fid][1]:  # surveyor_name
                    already_allocated.append(parcel_fid)

        if already_allocated:
            # Ask for confirmation
            reply = QMessageBox.question(self,
                                         QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Do you confirm?"),
                                         QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                    "Are you sure you want to remove the allocation of selected parcels?"),
                                         QMessageBox.Yes, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.discard_parcel_allocation(already_allocated)
        else:
            self.logger.info_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Selected parcels are not yet allocated, so we cannot reallocate them."))
Exemple #9
0
class Dependency(QObject):
    download_dependency_completed = pyqtSignal()
    download_dependency_progress_changed = pyqtSignal(int)  # progress

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self._downloading = False
        self._show_cursor = True
        self.dependency_name = ""
        self.fetcher_task = None

    def download_dependency(self, uri):
        if not uri:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "Dependency", "Invalid URL to download dependency."))

        self.logger.clear_message_bar()
        is_valid = self.check_if_dependency_is_valid()

        if is_valid:
            self.logger.debug(
                __name__,
                QCoreApplication.translate(
                    "Dependency",
                    "The {} dependency is already valid, so it won't be downloaded! (Dev, why did you asked to download it :P?)"
                    .format(self.dependency_name)))
            return

        if not self._downloading:  # Already downloading dependency?
            if is_connected(TEST_SERVER):
                self._downloading = True
                self.logger.clear_message_bar()
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "Dependency",
                        "A {} dependency will be downloaded...".format(
                            self.dependency_name)))
                self.fetcher_task = QgsNetworkContentFetcherTask(QUrl(uri))
                self.fetcher_task.begun.connect(self._task_begun)
                self.fetcher_task.progressChanged.connect(
                    self._task_progress_changed)
                self.fetcher_task.fetched.connect(
                    partial(self._save_dependency_file, self.fetcher_task))
                self.fetcher_task.taskCompleted.connect(self._task_completed)
                QgsApplication.taskManager().addTask(self.fetcher_task)
            else:
                self.logger.clear_message_bar()
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "Dependency",
                        "There was a problem connecting to Internet."))
                self._downloading = False

    def check_if_dependency_is_valid(self):
        raise NotImplementedError

    def _task_begun(self):
        if self._show_cursor:
            QApplication.setOverrideCursor(Qt.WaitCursor)

    def _task_progress_changed(self, progress):
        self.download_dependency_progress_changed.emit(progress)

    def _save_dependency_file(self, fetcher_task):
        raise NotImplementedError

    def _task_completed(self):
        if self._show_cursor:
            QApplication.restoreOverrideCursor()
        self.download_dependency_completed.emit()
class DockWidgetQualityRules(QgsDockWidget, DOCKWIDGET_UI):
    """
    Main UI for the Quality Rules module. It holds other panels.
    """
    trigger_action_emitted = pyqtSignal(str)  # action tag

    def __init__(self, controller, parent):
        super(DockWidgetQualityRules, self).__init__(parent)
        self.setupUi(self)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.__controller = controller
        self.__controller.quality_rule_layer_removed.connect(self.__layer_removed)

        self.logger = Logger()

        # Configure panels
        self.__general_results_panel = None
        self.__error_results_panel = None

        self.__main_panel = QualityRulesInitialPanelWidget(controller, self)
        self.widget.setMainPanel(self.__main_panel)

    def __layer_removed(self):
        self.logger.info_msg(__name__, QCoreApplication.translate("DockWidgetQualityRules",
                                                                  "'Quality rules panel' has been initialized because you just removed a required layer."))

        # Go back to initial panel when the user removes QR DB layers
        # Note this closes open signals on panels, reset controller variables
        # and even removes the whole QR DB group!
        self.widget.acceptAllPanels()

    def closeEvent(self, event):
        # Note this closes open signals on panels, reset controller variables
        # and even removes the whole QR DB group!
        self.widget.acceptAllPanels()

        self.close_dock_widget()

    def update_db_connection(self, db, ladm_col_db, db_source):
        self.close_dock_widget()  # The user needs to use the menus again, which will start everything from scratch

    def close_dock_widget(self):
        self.close()  # The user needs to use the menus again, which will start everything from scratch

    def show_general_results_panel(self, mode):
        """
        :params mode: EnumQualityRulePanelMode
        """
        with OverrideCursor(Qt.WaitCursor):
            self.__delete_general_result_panel()

            self.__general_results_panel = QualityRulesGeneralResultsPanelWidget(self.__controller, mode, self)
            self.__controller.total_progress_changed.connect(self.__general_results_panel.update_total_progress)
            self.__general_results_panel.panelAccepted.connect(self.__controller.reset_vars_for_general_results_panel)
            self.widget.showPanel(self.__general_results_panel)

            if mode == EnumQualityRulePanelMode.VALIDATE:
                self.logger.clear_message_bar()
                res, msg, db_qr = self.__controller.validate_qrs()
                if not res:
                    self.__general_results_panel.unblock_panel()
                    self.widget.acceptAllPanels()  # Go back to initial panel
                    self.logger.warning_msg(__name__, msg)

    def __delete_general_result_panel(self):
        if self.__general_results_panel is not None:
            try:
                self.widget.closePanel(self.__general_results_panel)
            except RuntimeError as e:  # Panel in C++ could be already closed...
                pass

            self.__general_results_panel = None

    def show_error_results_panel(self):
        with OverrideCursor(Qt.WaitCursor):
            if self.__error_results_panel is not None:
                try:
                    self.widget.closePanel(self.__error_results_panel)
                except RuntimeError as e:  # Panel in C++ could be already closed...
                    pass

                self.__error_results_panel = None

            self.__error_results_panel = QualityRulesErrorResultsPanelWidget(self.__controller, self)
            self.__error_results_panel.panelAccepted.connect(self.__controller.reset_vars_for_error_results_panel)
            self.widget.showPanel(self.__error_results_panel)
Exemple #11
0
class ToolBar(QObject):
    def __init__(self, iface, qgis_utils):
        QObject.__init__(self)
        self.iface = iface
        self.qgis_utils = qgis_utils
        self.logger = Logger()
        self.geometry = GeometryUtils()

    def build_boundary(self, db):
        QgsProject.instance().setAutoTransaction(False)
        layer = self.qgis_utils.get_layer_from_layer_tree(
            db, db.names.OP_BOUNDARY_T)
        use_selection = True

        if layer is None:
            self.logger.message_with_button_load_layer_emitted.emit(
                QCoreApplication.translate(
                    "ToolBar", "First load the layer {} into QGIS!").format(
                        db.names.OP_BOUNDARY_T),
                QCoreApplication.translate("ToolBar",
                                           "Load layer {} now").format(
                                               db.names.OP_BOUNDARY_T),
                db.names.OP_BOUNDARY_T, Qgis.Warning)
            return
        else:
            if layer.selectedFeatureCount() == 0:

                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are no selected boundaries. Do you want to use all the {} boundaries in the database?"
                    ).format(layer.featureCount()),
                    QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "ToolBar", "First select at least one boundary!"))
                    return

        if use_selection:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_selected_boundaries(
                db.names, layer, db.names.T_ID_F)
            num_boundaries = layer.selectedFeatureCount()
        else:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_boundaries(
                layer, db.names.T_ID_F)
            num_boundaries = layer.featureCount()

        if len(new_boundary_geoms) > 0:
            layer.startEditing(
            )  # Safe, even if layer is already on editing state

            # the boundaries that are to be replaced are removed
            layer.deleteFeatures(boundaries_to_del_ids)

            # Create features based on segment geometries
            new_fix_boundary_features = list()
            for boundary_geom in new_boundary_geoms:
                feature = QgsVectorLayerUtils().createFeature(
                    layer, boundary_geom)

                # TODO: Remove when local id and working space are defined
                feature.setAttribute(db.names.OID_T_LOCAL_ID_F, 1)
                feature.setAttribute(db.names.OID_T_NAMESPACE_F,
                                     db.names.OP_BOUNDARY_T)

                new_fix_boundary_features.append(feature)

            layer.addFeatures(new_fix_boundary_features)
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} feature(s) was(were) analyzed generating {} boundary(ies)!"
                ).format(num_boundaries, len(new_fix_boundary_features)))
            self.iface.mapCanvas().refresh()
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar", "There are no boundaries to build."))

    def fill_topology_table_pointbfs(self, db, use_selection=True):
        layers = {
            db.names.OP_BOUNDARY_T: {
                'name': db.names.OP_BOUNDARY_T,
                'geometry': None,
                LAYER: None
            },
            db.names.POINT_BFS_T: {
                'name': db.names.POINT_BFS_T,
                'geometry': None,
                LAYER: None
            },
            db.names.OP_BOUNDARY_POINT_T: {
                'name': db.names.OP_BOUNDARY_POINT_T,
                'geometry': None,
                LAYER: None
            }
        }

        self.qgis_utils.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[
                    db.names.OP_BOUNDARY_T][LAYER].selectedFeatureCount() == 0:
                if self.qgis_utils.get_layer_from_layer_tree(
                        db, db.names.OP_BOUNDARY_T) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one boundary!"
                        ).format(db.names.OP_BOUNDARY_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.OP_BOUNDARY_T),
                        db.names.OP_BOUNDARY_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected boundaries. Do you want to fill the '{}' table for all the {} boundaries in the database?"
                        ).format(
                            db.names.POINT_BFS_T, layers[
                                db.names.OP_BOUNDARY_T][LAYER].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar",
                                "First select at least one boundary!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} boundaries selected. Do you want to fill the '{table}' table just for the selected boundaries?\n\nIf you say 'No', the '{table}' table will be filled for all boundaries in the database."
                    ).format(selected=layers[db.names.OP_BOUNDARY_T]
                             [LAYER].selectedFeatureCount(),
                             table=db.names.POINT_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        bfs_features = layers[db.names.POINT_BFS_T][LAYER].getFeatures()

        # Get unique pairs id_boundary-id_boundary_point
        existing_pairs = [
            (bfs_feature[db.names.POINT_BFS_T_OP_BOUNDARY_F],
             bfs_feature[db.names.POINT_BFS_T_OP_BOUNDARY_POINT_F])
            for bfs_feature in bfs_features
        ]
        existing_pairs = set(existing_pairs)

        id_pairs = self.geometry.get_pair_boundary_boundary_point(
            layers[db.names.OP_BOUNDARY_T][LAYER],
            layers[db.names.OP_BOUNDARY_POINT_T][LAYER],
            db.names.T_ID_F,
            use_selection=use_selection)

        if id_pairs:
            layers[db.names.POINT_BFS_T][LAYER].startEditing()
            features = list()
            for id_pair in id_pairs:
                if not id_pair in existing_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.POINT_BFS_T][LAYER])
                    feature.setAttribute(db.names.POINT_BFS_T_OP_BOUNDARY_F,
                                         id_pair[0])
                    feature.setAttribute(
                        db.names.POINT_BFS_T_OP_BOUNDARY_POINT_F, id_pair[1])
                    features.append(feature)
            layers[db.names.POINT_BFS_T][LAYER].addFeatures(features)
            layers[db.names.POINT_BFS_T][LAYER].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                ).format(len(features), len(id_pairs), db.names.POINT_BFS_T,
                         len(id_pairs) - len(features), len(id_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_boundary_point found."))

    def fill_topology_tables_morebfs_less(self, db, use_selection=True):
        layers = {
            db.names.OP_PLOT_T: {
                'name': db.names.OP_PLOT_T,
                'geometry': QgsWkbTypes.PolygonGeometry,
                LAYER: None
            },
            db.names.MORE_BFS_T: {
                'name': db.names.MORE_BFS_T,
                'geometry': None,
                LAYER: None
            },
            db.names.LESS_BFS_T: {
                'name': db.names.LESS_BFS_T,
                'geometry': None,
                LAYER: None
            },
            db.names.OP_BOUNDARY_T: {
                'name': db.names.OP_BOUNDARY_T,
                'geometry': None,
                LAYER: None
            }
        }

        self.qgis_utils.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[db.names.OP_PLOT_T][LAYER].selectedFeatureCount() == 0:
                if self.qgis_utils.get_layer_from_layer_tree(
                        db,
                        db.names.OP_PLOT_T,
                        geometry_type=QgsWkbTypes.PolygonGeometry) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one plot!"
                        ).format(db.names.OP_PLOT_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.OP_PLOT_T),
                        db.names.OP_PLOT_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected plots. Do you want to fill the '{more}' and '{less}' tables for all the {all} plots in the database?"
                        ).format(more=db.names.MORE_BFS_T,
                                 less=db.names.LESS_BFS_T,
                                 all=layers[db.names.OP_PLOT_T]
                                 [LAYER].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar", "First select at least one plot!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} plots selected. Do you want to fill the '{more}' and '{less}' tables just for the selected plots?\n\nIf you say 'No', the '{more}' and '{less}' tables will be filled for all plots in the database."
                    ).format(selected=layers[db.names.OP_PLOT_T]
                             [LAYER].selectedFeatureCount(),
                             more=db.names.MORE_BFS_T,
                             less=db.names.LESS_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        more_bfs_features = layers[db.names.MORE_BFS_T][LAYER].getFeatures()
        less_features = layers[db.names.LESS_BFS_T][LAYER].getFeatures()

        # Get unique pairs id_boundary-id_plot in both tables
        existing_more_pairs = [
            (more_bfs_feature[db.names.MORE_BFS_T_OP_PLOT_F],
             more_bfs_feature[db.names.MORE_BFS_T_OP_BOUNDARY_F])
            for more_bfs_feature in more_bfs_features
        ]
        existing_more_pairs = set(existing_more_pairs)
        # Todo: Update when ili2db issue is solved.
        # Todo: When an abstract class only implements a concrete class, the name of the attribute is different if two or more classes are implemented.
        existing_less_pairs = [
            (less_feature[db.names.LESS_BFS_T_OP_PLOT_F],
             less_feature[db.names.LESS_BFS_T_OP_BOUNDARY_F])
            for less_feature in less_features
        ]
        existing_less_pairs = set(existing_less_pairs)

        id_more_pairs, id_less_pairs = self.geometry.get_pair_boundary_plot(
            layers[db.names.OP_BOUNDARY_T][LAYER],
            layers[db.names.OP_PLOT_T][LAYER],
            db.names.T_ID_F,
            use_selection=use_selection)
        if id_less_pairs:
            layers[db.names.LESS_BFS_T][LAYER].startEditing()
            features = list()
            for id_pair in id_less_pairs:
                if not id_pair in existing_less_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.LESS_BFS_T][LAYER])
                    feature.setAttribute(db.names.LESS_BFS_T_OP_PLOT_F,
                                         id_pair[0])
                    # Todo: Update LESS_BFS_T_OP_BOUNDARY_F by LESS_BFS_T_OP_BOUNDARY_F.
                    # Todo: When an abstract class only implements a concrete class, the name of the attribute is different if two or more classes are implemented.
                    feature.setAttribute(db.names.LESS_BFS_T_OP_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.LESS_BFS_T][LAYER].addFeatures(features)
            layers[db.names.LESS_BFS_T][LAYER].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_less_pairs),
                         db.names.LESS_BFS_T,
                         len(id_less_pairs) - len(features),
                         len(id_less_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.LESS_BFS_T))

        if id_more_pairs:
            layers[db.names.MORE_BFS_T][LAYER].startEditing()
            features = list()
            for id_pair in id_more_pairs:
                if not id_pair in existing_more_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.MORE_BFS_T][LAYER])
                    feature.setAttribute(db.names.MORE_BFS_T_OP_PLOT_F,
                                         id_pair[0])
                    feature.setAttribute(db.names.MORE_BFS_T_OP_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.MORE_BFS_T][LAYER].addFeatures(features)
            layers[db.names.MORE_BFS_T][LAYER].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_more_pairs),
                         db.names.MORE_BFS_T,
                         len(id_more_pairs) - len(features),
                         len(id_more_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.MORE_BFS_T))
Exemple #12
0
class DockWidgetQueries(QgsDockWidget, DOCKWIDGET_UI):
    def __init__(self, iface, controller, parent=None):
        super(DockWidgetQueries, self).__init__(None)
        self.setupUi(self)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.iface = iface
        self._controller = controller

        self.logger = Logger()
        self.app = AppInterface()

        self.canvas = iface.mapCanvas()
        self.active_map_tool_before_custom = None

        self._identify_tool = None

        self._fill_combos()

        self.btn_identify_plot.setIcon(
            QIcon(":/Asistente-LADM-COL/resources/images/spatial_unit.png"))

        self.tab_results.setTabEnabled(
            TAB_BASIC_INFO_INDEX,
            False)  # TODO: Remove when queries support LevCat 1.2
        self.tab_results.setTabEnabled(
            TAB_PHYSICAL_INFO_INDEX,
            False)  # TODO: Remove when queries support LevCat 1.2
        self.tab_results.setTabEnabled(
            TAB_ECONOMIC_INFO_INDEX,
            False)  # TODO: Remove when queries support LevCat 1.2
        self.tab_results.setCurrentIndex(
            TAB_LEGAL_INFO_INDEX
        )  # TODO: Remove when queries support LevCat 1.2

        # Set connections
        self._controller.close_view_requested.connect(self._close_dock_widget)

        self.btn_alphanumeric_query.clicked.connect(self._alphanumeric_query)
        self.cbo_parcel_fields.currentIndexChanged.connect(
            self._search_field_updated)
        self.btn_identify_plot.clicked.connect(self._btn_plot_toggled)
        self.btn_query_informality.clicked.connect(self._query_informality)
        self.btn_next_informal_parcel.clicked.connect(
            self._query_next_informal_parcel)
        self.btn_previous_informal_parcel.clicked.connect(
            self._query_previous_informal_parcel)

        # Context menu
        self._set_context_menus()

        # Create maptool
        self.maptool_identify = QgsMapToolIdentifyFeature(self.canvas)

        self._initialize_field_values_line_edit()
        self._update_informal_controls()

    def _search_field_updated(self, index=None):
        self._initialize_field_values_line_edit()

    def _initialize_field_values_line_edit(self):
        self.txt_alphanumeric_query.setLayer(self._controller.parcel_layer())
        idx = self._controller.parcel_layer().fields().indexOf(
            self.cbo_parcel_fields.currentData())
        self.txt_alphanumeric_query.setAttributeIndex(idx)

    def _set_context_menus(self):
        self.tree_view_basic.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_basic.customContextMenuRequested.connect(
            self._show_context_menu)

        self.tree_view_legal.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_legal.customContextMenuRequested.connect(
            self._show_context_menu)

        self.tree_view_physical.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_physical.customContextMenuRequested.connect(
            self._show_context_menu)

        self.tree_view_economic.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view_economic.customContextMenuRequested.connect(
            self._show_context_menu)

    def _close_dock_widget(self):
        # Deactivate custom tool and close dockwidget
        self._controller.disconnect_plot_layer()
        self._controller.disconnect_parcel_layer()

        self._initialize_tools(new_tool=None, old_tool=self.maptool_identify)
        self._btn_plot_toggled()
        self.close(
        )  # The user needs to use the menus again, which will start everything from scratch

    def _fill_combos(self):
        self.cbo_parcel_fields.clear()
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetQueries", "Parcel Number"),
            self._controller.parcel_number_name())
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetQueries",
                                       "Previous Parcel Number"),
            self._controller.previous_parcel_number_name())
        self.cbo_parcel_fields.addItem(
            QCoreApplication.translate("DockWidgetQueries",
                                       "Folio de Matrícula Inmobiliaria"),
            self._controller.fmi_name())

    def _initialize_tools(self, new_tool, old_tool):
        if self.maptool_identify == old_tool:
            # custom identify was deactivated
            try:
                self.canvas.mapToolSet.disconnect(self._initialize_tools)
            except TypeError as e:
                pass
            self.btn_identify_plot.setChecked(False)
        else:
            # custom identify was activated
            pass

    def _btn_plot_toggled(self):
        if self.btn_identify_plot.isChecked():
            self._prepare_identify_plot()
        else:
            # The button was toggled and deactivated, go back to the previous tool
            self.canvas.setMapTool(self.active_map_tool_before_custom)

    def _prepare_identify_plot(self):
        """
        Custom Identify tool was activated, prepare everything for identifying plots
        """
        self.active_map_tool_before_custom = self.canvas.mapTool()
        self.btn_identify_plot.setChecked(True)

        self.canvas.mapToolSet.connect(self._initialize_tools)

        self.maptool_identify.setLayer(self._controller.plot_layer())
        cursor = QCursor()
        cursor.setShape(Qt.PointingHandCursor)
        self.maptool_identify.setCursor(cursor)
        self.canvas.setMapTool(self.maptool_identify)

        try:
            self.maptool_identify.featureIdentified.disconnect()
        except TypeError as e:
            pass
        self.maptool_identify.featureIdentified.connect(
            self._search_data_by_plot)

    def _search_data_by_plot(self, plot_feature):
        plot_t_id = plot_feature[self._controller.t_id_name()]
        self.app.gui.flash_features(self._controller.plot_layer(),
                                    [plot_feature.id()])

        with OverrideCursor(Qt.WaitCursor):
            if not self.isVisible():
                self.show()

            self._search_data_by_component(plot_t_ids=[plot_t_id],
                                           zoom_and_select=False)
            self._controller.plot_layer().selectByIds([plot_feature.id()])

    def _search_data_by_component(self, **kwargs):
        """
        Perform the searches by component and fill tree views

        :param kwargs: A dict with search criteria.
        """
        self._controller.plot_layer().removeSelection()

        # Read zoom_and_select parameter and remove it from kwargs
        bZoom = False
        if 'zoom_and_select' in kwargs:
            bZoom = kwargs['zoom_and_select']
            del kwargs['zoom_and_select']

        if 'parcel_number' in kwargs and kwargs['parcel_number'] == NULL:
            self.logger.warning(
                __name__,
                QCoreApplication.translate(
                    "DockWidgetQueries",
                    "The parcel number is NULL! We cannot retrieve data for parcels with NULL parcel numbers."
                ))

        # records = self._controller.search_data_basic_info(**kwargs)
        # if bZoom:
        #     self._controller.zoom_to_resulting_plots(records)

        # self._setup_tree_view(self.tree_view_basic, records)

        records = self._controller.search_data_legal_info(**kwargs)
        self._setup_tree_view(self.tree_view_legal, records)

        # records = self._controller.search_data_physical_info(**kwargs)
        # self._setup_tree_view(self.tree_view_physical, records)

        # records = self._controller.search_data_economic_info(**kwargs)
        # self._setup_tree_view(self.tree_view_economic, records)

    def _setup_tree_view(self, tree_view, records):
        """
        Configure result tree views

        :param tree_view: Tree view to be updated
        :param records: List of dicts. A dict per plot: {id: 21, attributes: {...}}
        """
        tree_view.setModel(self._controller.create_model(records))
        self._collapse_tree_view_items(tree_view)
        self._add_thumbnails_to_tree_view(tree_view)

    def _collapse_tree_view_items(self, tree_view):
        """
        Collapse tree view items based on a property
        """
        tree_view.expandAll()
        for idx in tree_view.model().getCollapseIndexList():
            tree_view.collapse(idx)

    def _add_thumbnails_to_tree_view(self, tree_view):
        """
        Gets a list of model indexes corresponding to extFiles objects to show a preview
        """
        model = tree_view.model()
        for idx in model.getPixmapIndexList():
            url = model.data(idx, Qt.UserRole)['url']
            res, image = self._controller.download_image("{}{}".format(
                url, SUFFIX_GET_THUMBNAIL))
            if res:
                pixmap = QPixmap()
                pixmap.loadFromData(image)
                label = QLabel()
                label.setPixmap(pixmap)
                tree_view.setIndexWidget(idx, label)

    def _alphanumeric_query(self):
        option = self.cbo_parcel_fields.currentData()
        query = self.txt_alphanumeric_query.value()
        if query:
            if option == self._controller.fmi_name():
                self._search_data_by_component(parcel_fmi=query,
                                               zoom_and_select=True)
            elif option == self._controller.parcel_number_name():
                self._search_data_by_component(parcel_number=query,
                                               zoom_and_select=True)
            else:  # previous_parcel_number
                self._search_data_by_component(previous_parcel_number=query,
                                               zoom_and_select=True)
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate("DockWidgetQueries",
                                           "First enter a query"))

    def _show_context_menu(self, point):
        tree_view = self.sender()
        index = tree_view.indexAt(point)

        context_menu = QMenu("Context menu")

        index_data = index.data(Qt.UserRole)

        if index_data is None:
            return

        if "value" in index_data:
            action_copy = QAction(
                QCoreApplication.translate("DockWidgetQueries", "Copy value"))
            action_copy.triggered.connect(
                partial(self._controller.copy_value, index_data["value"]))
            context_menu.addAction(action_copy)
            context_menu.addSeparator()

        if "url" in index_data:
            action_open_url = QAction(
                QCoreApplication.translate("DockWidgetQueries", "Open URL"))
            action_open_url.triggered.connect(
                partial(self._controller.open_url, index_data["url"]))
            context_menu.addAction(action_open_url)
            context_menu.addSeparator()

        # Configure actions for tables/layers
        if "type" in index_data and "id" in index_data:
            table_name = index_data["type"]
            t_id = index_data["id"]

            if table_name == self._controller.parcel_layer_name():
                layer = self._controller.parcel_layer()
                self.app.core.activate_layer_requested.emit(layer)
            else:
                layer = self._controller.get_layer(table_name)

            if layer is not None:
                if layer.isSpatial():
                    action_zoom_to_feature = QAction(
                        QCoreApplication.translate(
                            "DockWidgetQueries",
                            "Zoom to {} with {}={}").format(
                                table_name, self._controller.t_id_name(),
                                t_id))
                    action_zoom_to_feature.triggered.connect(
                        partial(self._controller.zoom_to_feature, layer, t_id))
                    context_menu.addAction(action_zoom_to_feature)

                if table_name == self._controller.parcel_layer_name():
                    # We show a handy option to zoom to related plots
                    plot_ids = self._controller.get_plots_related_to_parcel(
                        t_id)
                    if plot_ids:
                        action_zoom_to_plots = QAction(
                            QCoreApplication.translate(
                                "DockWidgetQueries",
                                "Zoom to related plot(s)"))
                        action_zoom_to_plots.triggered.connect(
                            partial(self._controller.zoom_to_plots, plot_ids))
                        context_menu.addAction(action_zoom_to_plots)

                action_open_feature_form = QAction(
                    QCoreApplication.translate(
                        "DockWidgetQueries",
                        "Open form for {} with {}={}").format(
                            table_name, self._controller.t_id_name(), t_id))
                action_open_feature_form.triggered.connect(
                    partial(self._controller.open_feature_form, layer, t_id))
                context_menu.addAction(action_open_feature_form)

        if context_menu.actions():
            context_menu.exec_(tree_view.mapToGlobal(point))

    def _query_informality(self):
        first_parcel_number, current, total = self._controller.query_informal_parcels(
        )
        self._search_data_by_component(parcel_number=first_parcel_number,
                                       zoom_and_select=True)
        self._update_informal_controls(first_parcel_number, current, total)

        if not total:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "DockWidgetQueries",
                    "There are no informal parcels in this database!"))

    def _update_informal_controls(self, parcel_number='', current=0, total=0):
        """
        Update controls (reset labels, enable buttons if we have informality)
        """
        self._update_informal_labels(parcel_number, current, total)
        self.btn_query_informality.setText(
            QCoreApplication.translate("DockWidgetQueries", "Restart"
                                       ) if current else QCoreApplication.
            translate("DockWidgetQueries", "Start"))

        enable = total > 1  # At least 2 to enable buttons that traverse the parcels
        self.btn_next_informal_parcel.setEnabled(enable)
        self.btn_previous_informal_parcel.setEnabled(enable)

    def _update_informal_labels(self, parcel_number='', current=0, total=0):
        self.lbl_informal_parcel_number.setText(
            parcel_number if parcel_number != NULL else 'NULL')
        out_of = ''
        if current and total:
            out_of = QCoreApplication.translate("DockWidgetQueries",
                                                "{} out of {}").format(
                                                    current, total)
        self.lbl_informal_out_of_total.setText(out_of)

    def _query_next_informal_parcel(self):
        parcel_number, current, total = self._controller.get_next_informal_parcel(
        )
        self._search_data_by_component(parcel_number=parcel_number,
                                       zoom_and_select=True)
        self._update_informal_controls(parcel_number, current, total)

    def _query_previous_informal_parcel(self):
        parcel_number, current, total = self._controller.get_previous_informal_parcel(
        )
        self._search_data_by_component(parcel_number=parcel_number,
                                       zoom_and_select=True)
        self._update_informal_controls(parcel_number, current, total)

    def closeEvent(self, event):
        try:
            self.canvas.mapToolSet.disconnect(self._initialize_tools)
        except TypeError as e:
            pass
        self.canvas.setMapTool(self.active_map_tool_before_custom)
class WelcomeScreenDialog(QDialog, DIALOG_UI):
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.logger = Logger()
        self.help_strings = HelpStrings()

        #self.txt_help_page.setHtml(self.help_strings.DLG_WELCOME_SCREEN)
        #self.txt_help_page.anchorClicked.connect(self.save_template)

        self.finished.connect(self.finish_dialog)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.gbx_layout = QVBoxLayout()
        self.roles = RoleRegistry()
        self.dict_roles = self.roles.get_roles_info()
        checked = False
        active_role = self.roles.get_active_role()

        # Initialize radio buttons
        for k,v in self.dict_roles.items():
            radio = QRadioButton(v)
            if not checked:
                if k == active_role:
                    radio.setChecked(True)
                    checked = True
                    self.show_description(self.roles.get_role_description(k), checked)  # Initialize help page

            radio.toggled.connect(partial(self.show_description, self.roles.get_role_description(k)))
            self.gbx_layout.addWidget(radio)

        self.gbx_options.setLayout(self.gbx_layout)

    def finish_dialog(self, result):
        if result == 0:
            self.roles.set_active_default_role(emit_signal=False)  # Welcome dialog should not emit role_changed signal
        else:
            self.set_checked_role_active()

        self.logger.info_msg(__name__, QCoreApplication.translate("WelcomeScreenDialog",
                                                                  "The role '{}' is now active!").format(self.roles.get_active_role_name()))

    def show_description(self, description, checked):
        if checked:
            self.txt_help_page.setHtml("<span style=\" color:#545454;\">{}</span>".format(description))

    def set_checked_role_active(self):
        radio_checked = None
        for i in range(self.gbx_layout.count()):
            radio = self.gbx_layout.itemAt(i).widget()
            if radio.isChecked():
                radio_checked = radio.text()
                break

        for k, v in self.dict_roles.items():
            if v == radio_checked:
                self.roles.set_active_role(k, emit_signal=False)  # Welcome dialog should not emit role_changed signal
                break

    def show_help(self):
        show_plugin_help()
class ToolBar(QObject):
    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.logger = Logger()
        self.app = AppInterface()
        self.geometry = GeometryUtils()

    def build_boundary(self, db):
        QgsProject.instance().setAutoTransaction(False)
        layer = self.app.core.get_ladm_layer_from_qgis(
            db, db.names.LC_BOUNDARY_T, EnumLayerRegistryType.IN_LAYER_TREE)
        use_selection = True

        if layer is None:
            self.logger.message_with_button_load_layer_emitted.emit(
                QCoreApplication.translate(
                    "ToolBar", "First load the layer {} into QGIS!").format(
                        db.names.LC_BOUNDARY_T),
                QCoreApplication.translate("ToolBar",
                                           "Load layer {} now").format(
                                               db.names.LC_BOUNDARY_T),
                db.names.LC_BOUNDARY_T, Qgis.Warning)
            return
        else:
            if layer.selectedFeatureCount() == 0:

                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are no selected boundaries. Do you want to use all the {} boundaries in the database?"
                    ).format(layer.featureCount()),
                    QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "ToolBar", "First select at least one boundary!"))
                    return

        if use_selection:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_selected_boundaries(
                db.names, layer, db.names.T_ID_F)
            num_boundaries = layer.selectedFeatureCount()
        else:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_boundaries(
                layer, db.names.T_ID_F)
            num_boundaries = layer.featureCount()

        if len(new_boundary_geoms) > 0:
            layer.startEditing(
            )  # Safe, even if layer is already on editing state

            # the boundaries that are to be replaced are removed
            layer.deleteFeatures(boundaries_to_del_ids)

            # Create features based on segment geometries
            new_fix_boundary_features = list()
            for boundary_geom in new_boundary_geoms:
                feature = QgsVectorLayerUtils().createFeature(
                    layer, boundary_geom)

                # TODO: Remove when local id and namespace are defined
                feature.setAttribute(db.names.OID_T_LOCAL_ID_F, 1)
                feature.setAttribute(db.names.OID_T_NAMESPACE_F,
                                     db.names.LC_BOUNDARY_T)

                new_fix_boundary_features.append(feature)

            layer.addFeatures(new_fix_boundary_features)
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} feature(s) was(were) analyzed generating {} boundary(ies)!"
                ).format(num_boundaries, len(new_fix_boundary_features)))
            self.iface.mapCanvas().refresh()
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar", "There are no boundaries to build."))

    def fill_topology_table_pointbfs(self, db, use_selection=True):
        layers = {
            db.names.LC_BOUNDARY_T: None,
            db.names.POINT_BFS_T: None,
            db.names.LC_BOUNDARY_POINT_T: None
        }

        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[db.names.LC_BOUNDARY_T].selectedFeatureCount() == 0:
                if self.app.core.get_ladm_layer_from_qgis(
                        db, db.names.LC_BOUNDARY_T,
                        EnumLayerRegistryType.IN_LAYER_TREE) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one boundary!"
                        ).format(db.names.LC_BOUNDARY_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.LC_BOUNDARY_T),
                        db.names.LC_BOUNDARY_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected boundaries. Do you want to fill the '{}' table for all the {} boundaries in the database?"
                        ).format(
                            db.names.POINT_BFS_T,
                            layers[db.names.LC_BOUNDARY_T].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar",
                                "First select at least one boundary!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} boundaries selected. Do you want to fill the '{table}' table just for the selected boundaries?\n\nIf you say 'No', the '{table}' table will be filled for all boundaries in the database."
                    ).format(selected=layers[
                        db.names.LC_BOUNDARY_T].selectedFeatureCount(),
                             table=db.names.POINT_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        bfs_features = layers[db.names.POINT_BFS_T].getFeatures()

        # Get unique pairs id_boundary-id_boundary_point
        existing_pairs = [
            (bfs_feature[db.names.POINT_BFS_T_LC_BOUNDARY_F],
             bfs_feature[db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F])
            for bfs_feature in bfs_features
        ]
        existing_pairs = set(existing_pairs)

        tolerance = self.app.settings.tolerance
        id_pairs = self.geometry.get_pair_boundary_boundary_point(
            layers[db.names.LC_BOUNDARY_T],
            layers[db.names.LC_BOUNDARY_POINT_T],
            db.names.T_ID_F,
            use_selection=use_selection,
            tolerance=tolerance)

        if id_pairs:
            layers[db.names.POINT_BFS_T].startEditing()
            features = list()
            for id_pair in id_pairs:
                if not id_pair in existing_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.POINT_BFS_T])
                    feature.setAttribute(db.names.POINT_BFS_T_LC_BOUNDARY_F,
                                         id_pair[0])
                    feature.setAttribute(
                        db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F, id_pair[1])
                    features.append(feature)
            layers[db.names.POINT_BFS_T].addFeatures(features)
            layers[db.names.POINT_BFS_T].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                ).format(len(features), len(id_pairs), db.names.POINT_BFS_T,
                         len(id_pairs) - len(features), len(id_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_boundary_point found."))

    def fill_topology_tables_morebfs_less(self, db, use_selection=True):
        layers = {
            db.names.LC_PLOT_T: None,
            db.names.MORE_BFS_T: None,
            db.names.LESS_BFS_T: None,
            db.names.LC_BOUNDARY_T: None
        }

        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[db.names.LC_PLOT_T].selectedFeatureCount() == 0:
                if self.app.core.get_ladm_layer_from_qgis(
                        db, db.names.LC_PLOT_T,
                        EnumLayerRegistryType.IN_LAYER_TREE) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one plot!"
                        ).format(db.names.LC_PLOT_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.LC_PLOT_T),
                        db.names.LC_PLOT_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected plots. Do you want to fill the '{more}' and '{less}' tables for all the {all} plots in the database?"
                        ).format(
                            more=db.names.MORE_BFS_T,
                            less=db.names.LESS_BFS_T,
                            all=layers[db.names.LC_PLOT_T].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar", "First select at least one plot!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} plots selected. Do you want to fill the '{more}' and '{less}' tables just for the selected plots?\n\nIf you say 'No', the '{more}' and '{less}' tables will be filled for all plots in the database."
                    ).format(selected=layers[
                        db.names.LC_PLOT_T].selectedFeatureCount(),
                             more=db.names.MORE_BFS_T,
                             less=db.names.LESS_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        tolerance = self.app.settings.tolerance
        if tolerance:
            # We need to adjust input layers to take tolerance into account
            # Use the same configuration we use in quality rule 3004 (Plots should be covered by boundaries).
            layers[db.names.LC_PLOT_T] = self.app.core.adjust_layer(
                layers[db.names.LC_PLOT_T], layers[db.names.LC_PLOT_T],
                tolerance, True, use_selection)
            layers[db.names.LC_BOUNDARY_T] = self.app.core.adjust_layer(
                layers[db.names.LC_BOUNDARY_T], layers[db.names.LC_PLOT_T],
                tolerance, True)
            if use_selection:
                layers[db.names.LC_PLOT_T].selectAll(
                )  # Because this layer is already filtered by selected features

        # Get unique pairs id_boundary-id_plot in both tables
        existing_more_pairs = set([
            (more_bfs_feature[db.names.MORE_BFS_T_LC_PLOT_F],
             more_bfs_feature[db.names.MORE_BFS_T_LC_BOUNDARY_F])
            for more_bfs_feature in layers[db.names.MORE_BFS_T].getFeatures()
        ])
        existing_less_pairs = set([
            (less_feature[db.names.LESS_BFS_T_LC_PLOT_F],
             less_feature[db.names.LESS_BFS_T_LC_BOUNDARY_F])
            for less_feature in layers[db.names.LESS_BFS_T].getFeatures()
        ])

        id_more_pairs, id_less_pairs = self.geometry.get_pair_boundary_plot(
            layers[db.names.LC_BOUNDARY_T],
            layers[db.names.LC_PLOT_T],
            db.names.T_ID_F,
            use_selection=use_selection)
        if id_less_pairs:
            layers[db.names.LESS_BFS_T].startEditing()
            features = list()
            for id_pair in id_less_pairs:
                if not id_pair in existing_less_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.LESS_BFS_T])
                    feature.setAttribute(db.names.LESS_BFS_T_LC_PLOT_F,
                                         id_pair[0])
                    feature.setAttribute(db.names.LESS_BFS_T_LC_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.LESS_BFS_T].addFeatures(features)
            layers[db.names.LESS_BFS_T].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_less_pairs),
                         db.names.LESS_BFS_T,
                         len(id_less_pairs) - len(features),
                         len(id_less_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.LESS_BFS_T))

        if id_more_pairs:
            layers[db.names.MORE_BFS_T].startEditing()
            features = list()
            for id_pair in id_more_pairs:
                if not id_pair in existing_more_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.MORE_BFS_T])
                    feature.setAttribute(db.names.MORE_BFS_T_LC_PLOT_F,
                                         id_pair[0])
                    feature.setAttribute(db.names.MORE_BFS_T_LC_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.MORE_BFS_T].addFeatures(features)
            layers[db.names.MORE_BFS_T].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_more_pairs),
                         db.names.MORE_BFS_T,
                         len(id_more_pairs) - len(features),
                         len(id_more_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.MORE_BFS_T))
Exemple #15
0
class WizardMessagesManager:
    def __init__(self, wizard_tool_name, editing_layer_name):
        self.__WIZARD_TOOL_NAME = wizard_tool_name
        self.__logger = Logger()
        self.__editing_layer_name = editing_layer_name

    def show_wizard_closed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed.").format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_form_closed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "'{}' tool has been closed because you just closed the form.")\
            .format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_map_tool_changed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "'{}' tool has been closed because the map tool change.")\
            .format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_layer_removed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "'{}' tool has been closed because you just removed a required layer.")\
            .format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_feature_successfully_created_msg(self, feature_name, feature_id):
        message = QCoreApplication.translate(
                "WizardTranslations", "The new {} (t_id={}) was successfully created ")\
                .format(feature_name, feature_id)

        self.__logger.info_msg(__name__, message)

    def show_feature_not_found_in_layer_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed. Feature not found in layer {}... It's not possible create it.") \
            .format(self.__WIZARD_TOOL_NAME, self.__editing_layer_name)

        self.__logger.info_msg(__name__, message)

    def show_feature_not_found_in_layer_warning(self):
        self.__logger.warning(
            __name__, "Feature not found in layer {} ...".format(
                self.__editing_layer_name))

    def show_select_a_source_layer_warning(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "Select a source layer to set the field mapping to '{}'.") \
            .format(self.__editing_layer_name)
        self.__logger.warning_msg(__name__, message)