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")
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)))
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)
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."))
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)
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))
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))
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)