def testPrintMapFromTemplate(self): """Test that we can get a map to render in the template.""" myPath = os.path.join(TEST_DATA_DIR, 'landsat.tif') myFileInfo = QFileInfo(myPath) myRasterLayer = QgsRasterLayer(myFileInfo.filePath(), myFileInfo.completeBaseName()) myRenderer = QgsMultiBandColorRenderer( myRasterLayer.dataProvider(), 2, 3, 4 ) #mRasterLayer.setRenderer( rasterRenderer ) myPipe = myRasterLayer.pipe() assert myPipe.set(myRenderer), "Cannot set pipe renderer" QgsMapLayerRegistry.instance().addMapLayers([myRasterLayer]) myMapRenderer = QgsMapRenderer() myLayerStringList = [] myLayerStringList.append(myRasterLayer.id()) myMapRenderer.setLayerSet(myLayerStringList) myMapRenderer.setProjectionsEnabled(False) myComposition = QgsComposition(myMapRenderer) myFile = os.path.join(TEST_DATA_DIR, 'template-for-substitution.qpt') myTemplateFile = file(myFile, 'rt') myTemplateContent = myTemplateFile.read() myTemplateFile.close() myDocument = QDomDocument() myDocument.setContent(myTemplateContent) myComposition.loadFromTemplate(myDocument) # now render the map, first zooming to the raster extents myMap = myComposition.getComposerMapById(0) myMessage = ('Map 0 could not be found in template %s', myFile) assert myMap is not None, myMessage myExtent = myRasterLayer.extent() myMap.setNewExtent(myExtent) myImagePath = os.path.join(str(QDir.tempPath()), 'template_map_render_python.png') myPageNumber = 0 myImage = myComposition.printPageAsRaster(myPageNumber) myImage.save(myImagePath) assert os.path.exists(myImagePath), 'Map render was not created.' # Not sure if this is a predictable way to test but its quicker than # rendering. myFileSize = QFileInfo(myImagePath).size() myExpectedFileSize = 100000 myMessage = ('Expected file size to be greater than %s, got %s' ' for %s' % (myExpectedFileSize, myFileSize, myImagePath)) assert myFileSize > myExpectedFileSize, myMessage
def testPrintMapFromTemplate(self): """Test that we can get a map to render in the template.""" myPath = os.path.join(TEST_DATA_DIR, 'landsat.tif') myFileInfo = QFileInfo(myPath) myRasterLayer = QgsRasterLayer(myFileInfo.filePath(), myFileInfo.completeBaseName()) myRenderer = QgsMultiBandColorRenderer( myRasterLayer.dataProvider(), 2, 3, 4 ) #mRasterLayer.setRenderer( rasterRenderer ) myPipe = myRasterLayer.pipe() assert myPipe.set( myRenderer ), "Cannot set pipe renderer" QgsMapLayerRegistry.instance().addMapLayers([myRasterLayer]) myMapRenderer = QgsMapRenderer() myLayerStringList = [] myLayerStringList.append(myRasterLayer.id()) myMapRenderer.setLayerSet(myLayerStringList) myMapRenderer.setProjectionsEnabled(False) myComposition = QgsComposition(myMapRenderer) myFile = os.path.join(TEST_DATA_DIR, 'template-for-substitution.qpt') myTemplateFile = file(myFile, 'rt') myTemplateContent = myTemplateFile.read() myTemplateFile.close() myDocument = QDomDocument() myDocument.setContent(myTemplateContent) myComposition.loadFromTemplate(myDocument) # now render the map, first zooming to the raster extents myMap = myComposition.getComposerMapById(0) myMessage = ('Map 0 could not be found in template %s', myFile) assert myMap is not None, myMessage myExtent = myRasterLayer.extent() myMap.setNewExtent(myExtent) myImagePath = os.path.join(str(QDir.tempPath()), 'template_map_render_python.png') myPageNumber = 0 myImage = myComposition.printPageAsRaster(myPageNumber) myImage.save(myImagePath) assert os.path.exists(myImagePath), 'Map render was not created.' # Not sure if this is a predictable way to test but its quicker than # rendering. myFileSize = QFileInfo(myImagePath).size() myExpectedFileSize = 100000 myMessage = ('Expected file size to be greater than %s, got %s' ' for %s' % (myExpectedFileSize, myFileSize, myImagePath)) assert myFileSize > myExpectedFileSize, myMessage
def testWriteAsImageInvalidOutputPath(self): tmpName = "/this/is/invalid/file.tif" source = QgsRasterLayer(os.path.join(self.testDataDir, 'raster', 'byte.tif'), 'my', 'gdal') source.setContrastEnhancement(algorithm=QgsContrastEnhancement.NoEnhancement) self.assertTrue(source.isValid()) provider = source.dataProvider() fw = QgsRasterFileWriter(tmpName) self.assertEqual(fw.writeRaster(source.pipe(), provider.xSize(), provider.ySize(), provider.extent(), provider.crs()), QgsRasterFileWriter.CreateDatasourceError) del fw
def testWriteAsImage(self): tmpName = tempfile.mktemp(suffix='.tif') source = QgsRasterLayer(os.path.join(self.testDataDir, 'raster', 'byte.tif'), 'my', 'gdal') source.setContrastEnhancement(algorithm=QgsContrastEnhancement.NoEnhancement) self.assertTrue(source.isValid()) provider = source.dataProvider() fw = QgsRasterFileWriter(tmpName) self.assertEqual(fw.writeRaster(source.pipe(), provider.xSize(), provider.ySize(), provider.extent(), provider.crs()), QgsRasterFileWriter.NoError) ds = gdal.Open(tmpName) self.assertEqual(ds.RasterCount, 4) self.assertEqual(ds.GetRasterBand(1).Checksum(), 4672) self.assertEqual(ds.GetRasterBand(2).Checksum(), 4672) self.assertEqual(ds.GetRasterBand(3).Checksum(), 4672) self.assertEqual(ds.GetRasterBand(4).Checksum(), 4873) ds = None del fw os.unlink(tmpName)
class RFUDockWidget(QDockWidget, gui_dckwdgt_rfu_connector): closed = pyqtSignal() downloaded = pyqtSignal() uploaded = pyqtSignal() rfureset = pyqtSignal() def __init__(self, iface, canvas, project, conn=None, parent=None): super(RFUDockWidget, self).__init__(parent) self.setupUi(self) self.iface = iface self.canvas = canvas self.project = project self.conn = conn self.zone = None self.precision_class = [] self.ellips_acronym = [] self.dflt_ellips_acronym = None self.selected_ellips_acronym = None self.nature = [] self.typo_nature_som = [] self.typo_nature_lim = [] self.auth_creator = [] self.tol_same_pt = 0.0 self.config = Configuration() self.url_rfu = self.config.base_url_rfu self.url = None self.refdoss_cmt = None self.l_vertex = None self.l_edge = None self.layers = [self.l_vertex, self.l_edge] # Initialize dicts which contains changed datasets self.edges_added = {} self.edges_added_ft = {} self.vertices_added = {} self.vertices_added_ft = {} self.edges_removed = {} self.vertices_removed = {} self.edges_modified = {} self.vertices_modified = {} self.downloadPushButton.clicked.connect(self.on_downloaded) # self.permalinkLineEdit.returnPressed.connect(self.on_downloaded) self.projComboBox.currentIndexChanged.connect(self.set_destination_crs) # Loads permalinks into the permalink combox self.load_permalinks() # Create the WMS layer (from Geofoncier) self.wms_urlwithparams = 'contextualWMSLegend=0&crs=EPSG:4326&dpiMode=1&featureCount=10&format=image/png&layers=RFU&styles=default&url=' self.wms_urlwithparams += 'https://api.geofoncier.fr' # self.url_rfu self.wms_urlwithparams += '/referentielsoge/ogc/wxs/?' self.l_wms = QgsRasterLayer(self.wms_urlwithparams, 'Fond de plan RFU WMS', 'wms') # Define the contrast filter contrast_filter = QgsBrightnessContrastFilter() contrast_filter.setContrast(-100) # Assign filter to raster pipe self.l_wms.pipe().set(contrast_filter) # Add WMS layer to the registry self.project.addMapLayer(self.l_wms, True) # Apply changes to the WMS layer self.l_wms.triggerRepaint() def closeEvent(self, event): self.closed.emit() def on_downloaded(self): widget = self.iface.messageBar().createMessage( "Géofoncier", "Téléchargement du RFU.") progress_bar = QProgressBar() progress_bar.setMinimum(0) progress_bar.setMaximum(2) widget.layout().addWidget(progress_bar) self.iface.messageBar().pushWidget(widget) progress_bar.setValue(1) # https://pro.geofoncier.fr/index.php?¢re=-196406,5983255&context=metropole url = self.permalinkCmb.currentText() if not url: return self.abort_action(msg="Veuillez renseigner le permalien.") self.url = url try: self.download(self.url) except Exception as e: return self.abort_action(msg=str(e)) self.save_permalinks(self.url) progress_bar.setValue(2) self.iface.messageBar().clearWidgets() return def on_reset(self): # Ensure that the action is intentional resp = QMessageBox.question(self, reinit_msg[0], reinit_msg[1], QMessageBox.Yes, QMessageBox.No) if resp == QMessageBox.Yes: self.reset() self.rfureset.emit() return def on_uploaded(self): self.uploaded.emit() # Create message self.widget = self.iface.messageBar().createMessage( "Géofoncier", "Envoi des modifications.") self.progress_bar = QProgressBar() self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(3) self.widget.layout().addWidget(self.progress_bar) self.iface.messageBar().pushWidget(self.widget) self.progress_bar.setValue(1) # Specific dlg to manage the case of several doss with same ref self.refdoss_cmt = RefDossCmtEntry() self.refdoss_cmt.show() # Continue the process after capturing the dic of values self.refdoss_cmt.send_refdoss_cmt_vals.connect( self.on_uploaded_withref) # Continue the process after dlg validation def on_uploaded_withref(self, dic_vals): if dic_vals["ok"]: enr_ref_dossier = dic_vals["refdoss"] self.enr_cmt = dic_vals["cmt"] if not enr_ref_dossier: return self.abort_action( msg="Merci de renseigner une référence de dossier.") # Create correct comment if not self.enr_cmt: self.enr_cmt = cmt_dft % enr_ref_dossier else: self.enr_cmt += " - " + cmt_dft % enr_ref_dossier dossiers = self.conn.dossiersoge_dossiers(self.zone, enr_ref_dossier) dossiers_read = dossiers.read() # DEBUG: Export response as a text file # urlresp_to_file(dossiers_read) if dossiers.code != 200: return self.abort_action(msg=dossiers_read) data = json.loads(str(dossiers_read.decode('utf-8'))) nb_dossiers = data["count"] if nb_dossiers == 0: return self.abort_action( msg="Le dossier \'%s\' n'existe pas." % enr_ref_dossier) # Case of several same ref_dossier # In the case, the difference is made by enr_cab_createur if nb_dossiers >= 1: doss_infos = [] for doss in data["results"]: doss_info = [] doss_info.append(doss["enr_cab_createur"]) doss_info.append(doss["enr_ref_dossier"]) # doss_uri = doss.find(r"{http://www.w3.org/2005/Atom}link").attrib[r"href"].split(r"/")[-1][1:] # doss_info.append(doss_uri) # id = enr_api_dossier doss_info.append(doss["id"]) doss_info.append(doss["zone"]) doss_infos.append(doss_info) if len(doss_infos) > 1: self.doss_choice = MultiDossChoice(doss_infos) # Modal window self.doss_choice.setWindowModality(Qt.ApplicationModal) self.doss_choice.show() # Continue the process after capturing the dic of values self.doss_choice.send_refapidoss.connect( self.on_uploaded_proc) else: self.on_uploaded_proc(doss_info[2]) else: self.iface.messageBar().clearWidgets() # Launch the uploading after receiving the ref_api_doss def on_uploaded_proc(self, ref_api_doss): if ref_api_doss != "": self.progress_bar.setValue(2) # Stop editing mode for layer in self.layers: if layer.isEditable(): self.iface.setActiveLayer(layer) layer.commitChanges() # Check if dataset changes if (self.edges_added or self.vertices_added or self.edges_removed or self.vertices_removed or self.edges_modified or self.vertices_modified): pass else: return self.abort_action( msg="Aucune modification des données n'est détectée.") # Upload, reset and re-download datasets try: log = self.upload(enr_api_dossier=ref_api_doss, commentaire=self.enr_cmt) self.reset() self.permalinkCmb.setCurrentText(self.url) self.download(self.url) self.zoom_bbox() self.canvas.refresh() except Exception as e: self.reset() self.permalinkCmb.setCurrentText(self.url) self.download(self.url) self.zoom_bbox() self.canvas.refresh() return self.abort_action(msg="\n".join(e.args[0])) self.iface.messageBar().clearWidgets() return QMessageBox.information(self, r"Information", "\n".join(log)) # Case of dlg mutidoss_choice cancelled else: QMessageBox.information(self, multi_doss_canceled_msg[0], multi_doss_canceled_msg[1]) self.iface.messageBar().clearWidgets() def download(self, url): # Test if permalink is valid pattern = r"^(https?:\/\/(\w+[\w\-\.\:\/])+)\?((\&+)?(\w+)\=?([\w\-\.\:\,]+?)?)+(\&+)?$" if not re.match(pattern, self.url): raise Exception("Le permalien n'est pas valide.") # Extract params from url params = parse_qs(urlparse(self.url).query) # Check mandatory parameters try: context = str(params[r"context"][0]) center = params[r"centre"][0] except: raise Exception( "Les paramètres \'Context\' et \'Centre\' sont obligatoires.") auth_contexts = [ r"metropole", r"guadeloupe", r"stmartin", r"stbarthelemy", r"guyane", r"reunion", r"mayotte", r"martinique" ] # Check scale parameter try: scale = int(params[r"echelle"][0]) except: raise Exception("Le paramètre \'Echelle\' est obligatoire.") else: if scale > scale_limit: raise Exception(wrong_scale_txt.format(str(scale_limit))) # Check if context is valid if context not in auth_contexts: raise Exception( "La valeur \'%s\' est incorrecte.\n\n" "\'Context\' doit prentre une des %s valeurs suivantes: " "%s" % (context, len(auth_contexts), ", ".join(auth_contexts))) self.zone = context if self.zone in [ r"guadeloupe", r"stmartin", r"stbarthelemy", r"martinique" ]: self.zone = r"antilles" # Check if XY are valid if not re.match(r"^\-?\d+,\-?\d+$", center): raise Exception("Les coordonnées XY du centre sont incorrectes.") # Extract XY (¢re) xcenter = int(center.split(r",")[0]) ycenter = int(center.split(r",")[1]) # Compute the bbox xmin = xcenter - self.conn.extract_lim / 2 xmax = xcenter + self.conn.extract_lim / 2 ymin = ycenter - self.conn.extract_lim / 2 ymax = ycenter + self.conn.extract_lim / 2 # Transform coordinates in WGS84 bbox = tools.reproj(QgsRectangle(xmin, ymin, xmax, ymax), 3857, 4326, self.project) # Extract RFU (Send the request) resp = self.conn.extraction(bbox.xMinimum(), bbox.yMinimum(), bbox.xMaximum(), bbox.yMaximum()) resp_read = resp.read() # DEBUG: Export response as a text file # urlresp_to_file(resp_read) if resp.code != 200: raise Exception(resp_read) tree = EltTree.fromstring(resp_read) # Check if error err = tree.find(r"./erreur") if err: raise Exception(err.text) # Create the layer: "Masque d'extraction" self.l_bbox = QgsVectorLayer(r"Polygon?crs=epsg:4326&index=yes", "Zone de travail", r"memory") p_bbox = self.l_bbox.dataProvider() simple_symbol = QgsFillSymbol.createSimple({ r"color": r"116,97,87,255", r"style": r"b_diagonal", r"outline_style": r"no" }) renderer_bbox = QgsInvertedPolygonRenderer( QgsSingleSymbolRenderer(simple_symbol)) self.l_bbox.setRenderer(renderer_bbox) self.ft_bbox = QgsFeature() self.limit_area = QgsRectangle(bbox.xMinimum(), bbox.yMinimum(), bbox.xMaximum(), bbox.yMaximum()) self.ft_bbox.setGeometry(QgsGeometry.fromRect(self.limit_area)) p_bbox.addFeatures([self.ft_bbox]) self.l_bbox.updateFields() self.l_bbox.updateExtents() # Create layers.. self.layers = self.extract_layers(tree) self.l_vertex = self.layers[0] self.l_edge = self.layers[1] # Add layer to the registry self.project.addMapLayers([self.l_vertex, self.l_edge, self.l_bbox]) # Set extent # self.canvas.setExtent(QgsRectangle(bbox.xMinimum(), bbox.yMinimum(), # bbox.xMaximum(), bbox.yMaximum())) self.features_vertex_backed_up = \ dict((ft[r"fid"], ft) for ft in self.get_features(self.l_vertex)) self.features_edge_backed_up = \ dict((ft[r"fid"], ft) for ft in self.get_features(self.l_edge)) # Get Capabitilies resp = self.conn.get_capabilities(self.zone) resp_read = resp.read() # DEBUG # urlresp_to_file(resp_read) if resp.code != 200: raise Exception(resp_read) tree = EltTree.fromstring(resp_read) # Find tolerance to determine if 2 points are equals for entry in tree.findall(r"./tolerance"): self.tol_same_pt = float(entry.text) err = tree.find(r"./erreur") if err: raise Exception(err.text) for entry in tree.findall(r"./classe_rattachement/classe"): t = (entry.attrib[r"som_precision_rattachement"], entry.text) self.precision_class.append(t) for entry in tree.findall( r"./representation_plane_sommet_autorise/representation_plane_sommet" ): t = (entry.attrib[r"som_representation_plane"], entry.attrib[r"epsg_crs_id"], entry.text) self.ellips_acronym.append(t) # Added v2.1 << for entry in tree.findall(r"./typologie_nature_sommet/nature"): self.typo_nature_som.append(entry.text) for entry in tree.findall(r"./nature_sommet_conseille/nature"): self.nature.append(entry.text) for entry in tree.findall(r"./typologie_nature_limite/nature"): self.typo_nature_lim.append(entry.text) # >> for entry in tree.findall( r"./som_ge_createur_autorise/som_ge_createur"): t = (entry.attrib[r"num_ge"], entry.text) self.auth_creator.append(t) try: ft = next(ft for ft in self.l_vertex.getFeatures()) ft_attrib = tools.attrib_as_kv(ft.fields(), ft.attributes()) self.dflt_ellips_acronym = ft_attrib[r"som_representation_plane"] except: self.dflt_ellips_acronym = None for i, e in enumerate(self.ellips_acronym): self.projComboBox.addItem(e[2]) if not self.dflt_ellips_acronym and i == 0: self.project_crs = int(e[1]) if self.dflt_ellips_acronym == e[0]: # Check projection in combobox self.projComboBox.setCurrentIndex(i) # Then change the CRS in canvas epsg_str = "EPSG:" + str(int(e[1])) crs = QgsCoordinateReferenceSystem(epsg_str) self.project.setCrs(crs) self.project_crs = int(e[1]) # Calculate bbox in the project CRS (used for the scale limitation of the canvas) self.bbox_crsproject = tools.reproj( QgsRectangle(xmin, ymin, xmax, ymax), 3857, self.project_crs, self.project) # Zoom to bbox extents self.zoom_bbox() self.canvas.refresh() # Modified in v2.1 << # Add the list of possible values to the field som_nature map_predefined_vals_to_fld(self.l_vertex, "som_typologie_nature", self.typo_nature_som) # Add the list of possible values to the field som_precision_rattachement map_predefined_vals_to_fld(self.l_vertex, "som_precision_rattachement", self.precision_class, 0, 1) # Add the list of possible values to the field som_precision_rattachement map_predefined_vals_to_fld(self.l_vertex, "som_representation_plane", self.ellips_acronym, 0, 2) # Add the list of possible values to the field lim_typologie_nature map_predefined_vals_to_fld(self.l_edge, "lim_typologie_nature", self.typo_nature_lim) # Add the list of possible values to the field som_delimitation_publique false_true_lst = [('False', 'Faux'), ('True', 'Vrai')] map_predefined_vals_to_fld(self.l_vertex, "som_delimitation_publique", false_true_lst, 0, 1) # Add the list of possible values to the field lim_delimitation_publique map_predefined_vals_to_fld(self.l_edge, "lim_delimitation_publique", false_true_lst, 0, 1) # >> # Then, start editing mode.. for idx, layer in enumerate(self.layers): if not layer.isEditable(): layer.startEditing() if idx == 0: self.iface.setActiveLayer(layer) self.projComboBox.setDisabled(False) self.permalinkCmb.setDisabled(True) self.downloadPushButton.setDisabled(True) self.resetPushButton.setDisabled(False) self.uploadPushButton.setDisabled(False) self.downloadPushButton.clicked.disconnect(self.on_downloaded) # self.permalinkLineEdit.returnPressed.disconnect(self.on_downloaded) self.resetPushButton.clicked.connect(self.on_reset) self.uploadPushButton.clicked.connect(self.on_uploaded) # Activate the scale limitation for the canvas self.canvas.scaleChanged.connect(self.limit_cvs_scale) self.downloaded.emit() return True def reset(self): """Remove RFU layers.""" # Save (virtually) the changes in the layers # (to avoid alert messages when removing the layers) for layer in self.layers: if isinstance(layer, QgsVectorLayer): if layer.isEditable(): self.iface.setActiveLayer(layer) layer.commitChanges() # Remove RFU layers try: self.project.removeMapLayers( [self.l_vertex.id(), self.l_edge.id(), self.l_bbox.id()]) except: return # Remove eliminated lines layer if self.project.mapLayersByName(elimedge_lname): el_lyr = self.project.mapLayersByName(elimedge_lname)[0] self.iface.setActiveLayer(el_lyr) el_lyr.commitChanges() self.project.removeMapLayers([el_lyr.id()]) # Reset variable self.precision_class = [] self.ellips_acronym = [] self.dflt_ellips_acronym = None self.nature = [] self.typo_nature_lim = [] self.typo_nature_som = [] self.auth_creator = [] self.l_vertex = None self.l_edge = None self.layers = [self.l_vertex, self.l_edge] self.edges_added = {} self.vertices_added = {} self.edges_removed = {} self.vertices_removed = {} self.edges_modified = {} self.vertices_modified = {} self.tol_same_pt = 0.0 # Reset ComboBox which contains projections authorized self.projComboBox.clear() self.projComboBox.setDisabled(True) # Loads permalinks into the permalink combox self.load_permalinks() self.permalinkCmb.setDisabled(False) # self.permalinkLineEdit.returnPressed.connect(self.on_downloaded) self.downloadPushButton.setDisabled(False) self.downloadPushButton.clicked.connect(self.on_downloaded) self.resetPushButton.setDisabled(True) self.resetPushButton.clicked.disconnect(self.on_reset) self.uploadPushButton.setDisabled(True) self.uploadPushButton.clicked.disconnect(self.on_uploaded) return True def upload(self, enr_api_dossier=None, commentaire=None): """Upload data to Géofoncier REST API. On success returns the log messages (Array). """ # Set XML document root = EltTree.Element(r"rfu") first_vtx_kept = True first_edge_kept = True # Add to our XML document datasets which have been changed if self.vertices_added: for fid in self.vertices_added: # Check if vertex is out of the bbox to_export = check_vtx_outofbbox(self.vertices_added_ft[fid], self.ft_bbox) if to_export: tools.xml_subelt_creator(root, "sommet", data=self.vertices_added[fid], action=r"create") # If vertex is out of the bbox else: # Create a new layer to store the vertices non exported if first_vtx_kept: if layer_exists(vtx_outofbbox_lname, self.project): vtx_outofbbox_lyr = self.project.mapLayersByName( vtx_outofbbox_lname)[0] else: vtx_outofbbox_lyr = create_vtx_outofbbox_lyr() # Add the vertex to this layer if not vtx_outofbbox_lyr.isEditable(): vtx_outofbbox_lyr.startEditing() vtx_outofbbox_lyr.addFeature(self.vertices_added_ft[fid]) first_vtx_kept = False if self.edges_added: for fid in self.edges_added: # Check if edge is out of the bbox to_export = check_edge_outofbbox(self.edges_added_ft[fid], self.ft_bbox) if to_export: tools.xml_subelt_creator(root, "limite", data=self.edges_added[fid], action=r"create") # If edge is out of the bbox else: # Create a new layer to store the edges non exported if first_edge_kept: if layer_exists(edge_outofbbox_lname, self.project): edge_outofbbox_lyr = self.project.mapLayersByName( edge_outofbbox_lname)[0] else: edge_outofbbox_lyr = create_edge_outofbbox_lyr() # Add the edge to this layer if not edge_outofbbox_lyr.isEditable(): edge_outofbbox_lyr.startEditing() edge_outofbbox_lyr.addFeature(self.edges_added_ft[fid]) first_edge_kept = False if self.vertices_removed: for fid in self.vertices_removed: tools.xml_subelt_creator(root, "sommet", data=self.vertices_removed[fid], action=r"delete") if self.edges_removed: for fid in self.edges_removed: tools.xml_subelt_creator(root, "limite", data=self.edges_removed[fid], action=r"delete") if self.vertices_modified: for fid in self.vertices_modified: tools.xml_subelt_creator(root, "sommet", data=self.vertices_modified[fid], action=r"update") if self.edges_modified: for fid in self.edges_modified: tools.xml_subelt_creator(root, "limite", data=self.edges_modified[fid], action=r"update") # Create a new changeset Id changeset_id = self.create_changeset(enr_api_dossier=enr_api_dossier, commentaire=commentaire) # Add changeset value in our XML document root.attrib[r"changeset"] = changeset_id # Send data edit = self.conn.edit(self.zone, EltTree.tostring(root)) if edit.code != 200: edit_read = edit.read() # DEBUG # urlresp_to_file(edit_read) err_tree = EltTree.fromstring(edit_read) msgs_log = [] for log in err_tree.iter(r"log"): msgs_log.append("%s: %s" % (log.attrib["type"], log.text)) raise Exception(msgs_log) tree = EltTree.fromstring(edit.read()) err = tree.find(r"./erreur") if err: debug_msg('DEBUG', "erreur: %s", (str(err))) err_tree = EltTree.fromstring(err) msgs_log = [] for log in err_tree.iter(r"log"): msgs_log.append("%s: %s" % (log.attrib["type"], log.text)) raise Exception(msgs_log) # Returns log info msgs_log = [] for log in tree.iter(r"log"): msgs_log.append("%s: %s" % (log.attrib["type"], log.text)) # Close the changeset self.destroy_changeset(changeset_id) # Reset all self.edges_added = {} self.edges_added_ft = {} self.vertices_added = {} self.vertices_added_ft = {} self.edges_removed = {} self.vertices_removed = {} self.edges_modified = {} self.vertices_modified = {} # Alert message if elements out of bbox msg_outbbox = "" if not first_vtx_kept: msg_outbbox = msg_outbbox_vtx.format(vtx_outofbbox_lname) if not first_edge_kept: if msg_outbbox != "": msg_outbbox += "<br>" msg_outbbox += msg_outbbox_edge.format(edge_outofbbox_lname) if msg_outbbox != "": self.canvas.refresh() m_box = mbox_w_params(tl_atn, txt_msg_outbbox, msg_outbbox) m_box.exec_() return msgs_log def create_changeset(self, enr_api_dossier=None, commentaire=None): """Open a new changeset from Géofoncier API. On success, returns the new changeset id. """ opencs = self.conn.open_changeset(self.zone, enr_api_dossier=enr_api_dossier, commentaire=commentaire) if opencs.code != 200: raise Exception(opencs.read()) tree = EltTree.fromstring(opencs.read()) err = tree.find(r"./log") if err: raise Exception(err.text) # treeterator = list(tree.getiterator(tag=r"changeset")) # Python 3.9 -> getiterator deprecated treeterator = list(tree.iter(tag=r"changeset")) # We should get only one changeset if len(treeterator) != 1: raise Exception("Le nombre de \'changeset\' est incohérent.\n" "Merci de contacter l'administrateur Géofoncier.") return treeterator[0].attrib[r"id"] def destroy_changeset(self, id): """Close a changeset.""" closecs = self.conn.close_changeset(self.zone, id) if closecs.code != 200: raise Exception(closecs.read()) tree = EltTree.fromstring(closecs.read()) err = tree.find(r"./log") if err: raise Exception(err.text) return True def abort_action(self, msg=None): for layer in self.layers: if layer and not layer.isEditable(): layer.startEditing() # Clear message bar self.iface.messageBar().clearWidgets() if msg: return QMessageBox.warning(self, r"Attention", msg) return def extract_layers(self, tree): """Return a list of RFU layers.""" # Create vector layers.. l_vertex = QgsVectorLayer(r"Point?crs=epsg:4326&index=yes", "Sommet RFU", r"memory") l_edge = QgsVectorLayer(r"LineString?crs=epsg:4326&index=yes", "Limite RFU", r"memory") p_vertex = l_vertex.dataProvider() p_edge = l_edge.dataProvider() # Define default style renderer.. renderer_vertex = QgsRuleBasedRenderer(QgsMarkerSymbol()) vertex_root_rule = renderer_vertex.rootRule() # Modified in v2.1 (som_nature replaced by som_typologie_nature) >> vertex_rules = ( (("Borne, borne à puce, pierre, piquet, clou ou broche"), ("$id >= 0 AND \"som_typologie_nature\" IN ('Borne'," "'Borne à puce', 'Pierre', 'Piquet', 'Clou ou broche')"), r"#EC0000", 2.2), (("Axe cours d'eau, axe fossé, haut de talus, pied de talus"), ("$id >= 0 AND \"som_typologie_nature\" IN ('Axe cours d\'\'eau'," "'Axe fossé', 'Haut de talus', 'Pied de talus')"), r"#EE8012", 2.2), (("Angle de bâtiment, axe de mur, angle de mur, " "angle de clôture, pylône et toute autre valeur"), ("$id >= 0 AND \"som_typologie_nature\" NOT IN ('Borne'," "'Borne à puce', 'Pierre', 'Piquet', 'Clou ou broche'," "'Axe cours d\'\'eau', 'Axe fossé', 'Haut de talus'," "'Pied de talus')"), r"#9784EC", 2.2), ("Temporaire", r"$id < 0", "cyan", 2.4), ("Point nouveau à traiter car proche d'un existant", r"point_rfu_proche is not null", "#bcff03", 3)) # >> for label, expression, color, size in vertex_rules: rule = vertex_root_rule.children()[0].clone() rule.setLabel(label) rule.setFilterExpression(expression) rule.symbol().setColor(QColor(color)) rule.symbol().setSize(size) vertex_root_rule.appendChild(rule) vertex_root_rule.removeChildAt(0) l_vertex.setRenderer(renderer_vertex) renderer_edge = QgsRuleBasedRenderer(QgsLineSymbol()) edge_root_rule = renderer_edge.rootRule() # Modified in v2.1 (lim_typologie_nature added) << edge_rules = (("Limite privée", "$id >= 0 AND \"lim_typologie_nature\" = '" + lim_typo_nat_vals[0] + "'", "#0A0AFF", 0.5), ("Limite naturelle", "$id >= 0 AND \"lim_typologie_nature\" = '" + lim_typo_nat_vals[1] + "'", "#aa876d", 0.5), ("Temporaire", "$id < 0", "cyan", 1)) # >> for label, expression, color, width in edge_rules: rule = edge_root_rule.children()[0].clone() rule.setLabel(label) rule.setFilterExpression(expression) rule.symbol().setColor(QColor(color)) rule.symbol().setWidth(width) edge_root_rule.appendChild(rule) edge_root_rule.removeChildAt(0) l_edge.setRenderer(renderer_edge) # Add fields.. p_vertex.addAttributes(vtx_atts) p_edge.addAttributes(edge_atts) # Add features from xml tree.. # ..to vertex layer.. fts_vertex = [] for e in tree.findall(r"sommet"): ft_vertex = QgsFeature() ft_vertex.setGeometry(QgsGeometry.fromWkt(e.attrib[r"geometrie"])) _id_noeud = int(e.attrib[r"id_noeud"]) _version = int(e.attrib[r"version"]) som_ge_createur = str(e.find(r"./som_ge_createur").text) som_nature = str(e.find(r"./som_nature").text) som_prec_rattcht = int( e.find(r"./som_precision_rattachement").text) som_coord_est = float(e.find(r"./som_coord_est").text) som_coord_nord = float(e.find(r"./som_coord_nord").text) som_repres_plane = str(e.find(r"./som_representation_plane").text) som_tolerance = float(e.find(r"./som_tolerance").text) # Field used to store the attestation_qualite value # when modifying a vertex ("false" or "true") attestation_qualite = "false" som_delim_pub = str(e.find(r"./som_delimitation_publique").text) som_typo_nature = str(e.find(r"./som_typologie_nature").text) ft_vertex.setAttributes([ _id_noeud, _version, som_ge_createur, som_delim_pub, som_typo_nature, som_nature, som_prec_rattcht, som_coord_est, som_coord_nord, som_repres_plane, som_tolerance, attestation_qualite, NULL ]) fts_vertex.append(ft_vertex) # ..to edge layer.. fts_edge = [] for e in tree.findall(r"limite"): ft_edge = QgsFeature() ft_edge.setGeometry(QgsGeometry.fromWkt(e.attrib[r"geometrie"])) _id_arc = int(e.attrib[r"id_arc"]) _version = int(e.attrib[r"version"]) lim_ge_createur = str(e.find(r"./lim_ge_createur").text) lim_typo_nature = str(e.find(r"./lim_typologie_nature").text) lim_delim_pub = str(e.find(r"./lim_delimitation_publique").text) ft_edge.setAttributes([ _id_arc, _version, lim_ge_createur, lim_delim_pub, lim_typo_nature ]) fts_edge.append(ft_edge) # Add features to layers.. p_vertex.addFeatures(fts_vertex) p_edge.addFeatures(fts_edge) # Update fields.. l_vertex.updateFields() l_edge.updateFields() # Update layer's extent.. l_vertex.updateExtents() l_edge.updateExtents() # Check if valid.. if not l_vertex.isValid() or not l_edge.isValid(): raise Exception( "Une erreur est survenue lors du chargement de la couche.") # Then return layers.. return [l_vertex, l_edge] def get_features(self, layer): features = [] for ft in layer.getFeatures(): attributes = tools.attrib_as_kv(ft.fields(), ft.attributes()) attributes[r"fid"] = ft.id() features.append(attributes) return features def remove_features(self, layer_id, fids): for fid in fids: if layer_id == self.l_edge.id( ) and fid in self.features_edge_backed_up: self.edges_removed[fid] = self.features_edge_backed_up[fid] if layer_id == self.l_vertex.id( ) and fid in self.features_vertex_backed_up: self.vertices_removed[fid] = self.features_vertex_backed_up[ fid] def add_features(self, layer_id, features): for ft in features: attrib = tools.attrib_as_kv(ft.fields(), ft.attributes(), qgsgeom=ft.geometry()) if layer_id == self.l_vertex.id(): self.vertices_added[ft.id()] = attrib self.vertices_added_ft[ft.id()] = ft if layer_id == self.l_edge.id(): self.edges_added[ft.id()] = attrib self.edges_added_ft[ft.id()] = ft def modify_feature(self, layer_id, feature, qgsgeom=None): if qgsgeom: f = tools.attrib_as_kv(feature.fields(), feature.attributes(), qgsgeom=qgsgeom) else: f = tools.attrib_as_kv(feature.fields(), feature.attributes()) if self.l_edge.id() == layer_id: if feature.id() not in self.features_edge_backed_up: return self.edges_modified[feature.id()] = f if self.l_vertex.id() == layer_id: if feature.id() not in self.features_vertex_backed_up: return self.vertices_modified[feature.id()] = f def set_destination_crs(self, j): epsg = 4326 # by default for i, e in enumerate(self.ellips_acronym): if i == j: self.selected_ellips_acronym = e[0] epsg = int(e[1]) continue epsg_str = "EPSG:" + str(epsg) crs = QgsCoordinateReferenceSystem(epsg_str) self.project.setCrs(crs) # Stop zoom when the scale limit is exceeded def limit_cvs_scale(self): if self.canvas.scale() > cvs_scale_limit: self.disconn_scale_limit() self.zoom_bbox() self.canvas.zoomScale(cvs_scale_limit) self.canvas.scaleChanged.connect(self.limit_cvs_scale) def zoom_bbox(self): self.canvas.setExtent( QgsRectangle(self.bbox_crsproject.xMinimum(), self.bbox_crsproject.yMinimum(), self.bbox_crsproject.xMaximum(), self.bbox_crsproject.yMaximum())) def disconn_scale_limit(self): self.canvas.scaleChanged.disconnect(self.limit_cvs_scale) def conn_scale_limit(self): self.canvas.scaleChanged.connect(self.limit_cvs_scale) # Loads permalinks from the json file into the combobox def load_permalinks(self): try: self.json_path = os.path.join(os.path.dirname(__file__), r"permalinks.json") except IOError as error: raise error with codecs.open(self.json_path, encoding='utf-8', mode='r') as json_file: json_permalinks = json.load(json_file) self.permalinks = json_permalinks[r"permalinks"] current_permalink_idx = json_permalinks[r"current_permalink_idx"] if len(self.permalinks) > 0: self.permalinkCmb.clear() for idx, permalink in enumerate(self.permalinks): self.permalinkCmb.addItem(permalink) self.permalinkCmb.setCurrentIndex(current_permalink_idx) # self.permalinkCmb.lineEdit().selectAll() # Save permalinks in the json file (limited to 5 permalinks) def save_permalinks(self, permalink): # Update the json file json_permalinks = {} if permalink not in self.permalinks: # Update the permalinks list if len(self.permalinks) == 5: del self.permalinks[0] self.permalinks.append(permalink) json_permalinks["current_permalink_idx"] = len(self.permalinks) - 1 else: json_permalinks[ "current_permalink_idx"] = self.permalinkCmb.currentIndex() json_permalinks["permalinks"] = self.permalinks with codecs.open(self.json_path, encoding='utf-8', mode='w') as json_file: json_file.write( json.dumps(json_permalinks, indent=4, separators=(',', ': '), ensure_ascii=False))
def processAlgorithm(self, parameters, context, feedback): # get input variables raster = self.parameterAsFile(parameters, self.INPUT, context) color_ramp = self.parameterAsEnum(parameters, self.COLORRAMP, context) colors = self.color_ramps[self.colors_list[color_ramp]] min = self.parameterAsInt(parameters, self.MIN, context) max = self.parameterAsInt(parameters, self.MAX, context) z_pos_down = self.parameterAsBoolean(parameters, self.Z_POS_DOWN, context) # set new default values in config feedback.pushConsoleInfo( self.tr(f'Storing new default settings in config...')) self.config.set(self.module, 'min', min) self.config.set(self.module, 'max', max) self.config.set(self.module, 'color_ramp', color_ramp) # get file info base_path, base_name, ext = utils.get_info_from_path(raster) # BATHY: # load grid feedback.pushConsoleInfo( self.tr(f'Creating new raster layer [ {base_name} ]...')) dem_layer = QgsRasterLayer(raster, base_name) # test if the files loads properly if not dem_layer.isValid(): raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) # create color scale values feedback.pushConsoleInfo(self.tr(f'Creating color ramp...')) n_values = len(colors) width = max - min step = width / (n_values - 1) values = [] value = min for i in range(n_values): values.append(value) value = value + step # create color_ramp ramp = [] for i, item in enumerate(colors): ramp.append( QgsColorRampShader.ColorRampItem(values[i], QColor(str(item)), str(values[i]))) color_ramp = QgsColorRampShader() color_ramp.setColorRampItemList(ramp) color_ramp.setColorRampType(QgsColorRampShader.Interpolated) # create shader and set color_ramp feedback.pushConsoleInfo(self.tr(f'Creating raster shader...')) shader = QgsRasterShader() shader.setRasterShaderFunction(color_ramp) # create renderer feedback.pushConsoleInfo(self.tr(f'Creating raster renderer...')) renderer = QgsSingleBandPseudoColorRenderer(dem_layer.dataProvider(), dem_layer.type(), shader) # set min max values renderer.setClassificationMin(min) renderer.setClassificationMax(max) # apply renderer to layer dem_layer.setRenderer(renderer) # apply brightness & contrast of layer feedback.pushConsoleInfo(self.tr(f'Adjusting display filters...')) brightness_filter = QgsBrightnessContrastFilter() brightness_filter.setBrightness(-20) brightness_filter.setContrast(10) dem_layer.pipe().set(brightness_filter) # apply resample filter (Bilinear) feedback.pushConsoleInfo(self.tr(f'Setting up resampling...')) resample_filter = dem_layer.resampleFilter() resample_filter.setZoomedInResampler(QgsBilinearRasterResampler()) resample_filter.setZoomedOutResampler(QgsBilinearRasterResampler()) # create group with layer base_name feedback.pushConsoleInfo(self.tr(f'Creating layer group...')) root = context.project().layerTreeRoot() bathy_group = root.addGroup(base_name) # add bathy layer to group bathy_group.insertChildNode(1, QgsLayerTreeLayer(dem_layer)) # add bathy layer to project dem_layer.triggerRepaint() context.project().addMapLayer(dem_layer, False) # 50% done feedback.setProgress(50) # HILLSHADE: # load grid again with layer style file style_hillshade.qml feedback.pushConsoleInfo( self.tr( f'Creating new hillshade layer [ {base_name}_hillshade ]...')) hillshade_layer = QgsRasterLayer(raster, base_name + '_hillshade') # if raster is geographic, load hillshade_geo style (different exaggeration) # if raster is Z positive down, load *_pos_down_* style feedback.pushConsoleInfo(self.tr(f'Setting hillshade style...\n')) if dem_layer.crs().isGeographic() and not z_pos_down: hillshade_layer.loadNamedStyle(self.style_hillshade_geo) elif dem_layer.crs().isGeographic() and z_pos_down: hillshade_layer.loadNamedStyle(self.style_hillshade_pos_down_geo) # else load hillste_prj style elif z_pos_down: hillshade_layer.loadNamedStyle(self.style_hillshade_pos_down_prj) else: hillshade_layer.loadNamedStyle(self.style_hillshade_prj) # add hillshade layer to group bathy_group.insertChildNode(0, QgsLayerTreeLayer(hillshade_layer)) # add hillshade layer to project hillshade_layer.triggerRepaint() context.project().addMapLayer(hillshade_layer, False) # 100% done feedback.setProgress(100) feedback.pushInfo( self.tr(f'{utils.return_success()}! Grid loaded successfully!\n')) result = { self.GROUP: bathy_group, self.DEM_LAYER: dem_layer, self.HILLSHADE_LAYER: hillshade_layer } return result