1

I'm trying to load styles that are stored in a PostGIS database. We have them organised in sets, for each layer that is in the DB. I added comments in the code.

This code was previously 2 lines. Fetch the style names from the database and apply it to the layer using layer.loadNamedStyle(), but that didn't work.

In the mean time I'm using a styleManager() with bits and pieces I collected from posts on StackExchange. But it still doesn't work and I'm at a loss to figure out why.

I tried to keep the code snippet limited to the essential part, if you need to see more of the code, please let me know.

def reload_layers(self):
    self.iface.mapCanvas().layers()

    geometry_column = "geom"
    self.layer_styles = {}

    # remove the layers that will be reloaded from the Legend
    for layer in QgsProject.instance().mapLayers():
        for table in self.tablenames:
            if layer.title().lower().startswith(table):
                QgsProject.instance().removeMapLayer(layer)
    for table in self.tablenames:
        uri = QgsDataSourceUri()
        uri.setConnection(DB_ADDRESS, DB_PORT, DB_NAME, DB_USERNAME, DB_PASSWORD)
        uri.setDataSource("main", table, geometry_column)
        layer = QgsVectorLayer(uri.uri(False), table, "postgres")
        if not self.layer_styles:
            self.populate_layer_styles(layer)
        if not layer.isValid():
            QgsMessageLog.logMessage("Layer failed to load", level=Qgis.Critical)
        else:
            # add the layer to the Layers Pane
            QgsProject.instance().addMapLayer(layer)
            layer = QgsProject.instance().layerTreeRoot().findLayer(layer)
            layer.setExpanded(False)
            layer.visibilityChanged.connect(self.update_layout_widgets)
            layer.geometryChanged.connect(self.update_layout_widgets)
            if layer.name().lower().startswith('mow_segment'):
                QgsProject.instance().layerTreeRoot().findLayer(layer).setItemVisibilityChecked(False)
            layer.triggerRepaint()

def populate_layer_styles(self, layer):
    # listStylesInDatabase gives us the names of the styles available in PostGIS, wonderful
    for style in layer.listStylesInDatabase()[2]:
        # We organised them in sets, the 4th character contains a number
        style_set = style[3]
        if style_set not in self.layer_styles:
            self.layer_styles[style_set] = {}
        # Now for the odd part, for each layer, we have to set up a style manager
        style_manager = layer.styleManager()
        from PyQt5.QtXml import QDomDocument
        # it gets weirder, we have to fetch the definition of the style from the database
        query = QtSql.QSqlQuery()
        query.prepare("SELECT styleqml FROM public.layer_styles WHERE stylename='{}'".format(style))
        query.exec_()
        result = []
        time.sleep(0.3)
        while query.next():
            result.append(query.value(0))

        if result:
            styleqml = result[0]
            style_doc = QDomDocument()
            style_doc.setContent(styleqml)
            # and import the definition into the style manager
            # But it seems like addStyleFromLayer adds the currently active style
            # So we import the style definition into the layer
            layer.importNamedStyle(style_doc)
            # Load and activate it
            layer.loadNamedStyle(style)
            # And then add it to the style manager
            style_manager.addStyleFromLayer(style)
            # And keep a reference to the style manager
            self.layer_styles[style_set][style] = style_manager
            # Unfortunately all that doesn't work
    # Update radio buttons on the interface
    for style_set in self.layer_styles:
        if style_set not in self.dockwidget.rb_styles.button:
            self.dockwidget.rb_styles.add_button(str(style_set), self.activate_style_set)

def activate_style_set(self, style_set):
    # print(style_set.text())
    for layer in QgsProject.instance().mapLayers().values():
        # only for vector layers
        if layer.type() != 0:
            continue
        # Extract table name using light regex
        table_re = re.compile(r'table="main"."(mow_.+?)"')
        table = table_re.search(layer.source())
        if table:
            table = table.group(1)
        else:
            continue
        # And compare it with layer names used for reloading the layers
        if table in self.tablenames:
            style = 'set' + style_set.text() + '-' + table
            # print(style)
            # print(layer.loadNamedStyle(style))
            # style_manager = layer.styleManager()
            print(style_set.text(), style)
            print(self.layer_styles[style_set.text()])
            style_manager = self.layer_styles[style_set.text()][style]
            # style_manager.addStyleFromLayer(style)
            # print(style_manager.mapLayerStyles())
            # # print(style_manager.layer().name())
            # print(style_manager.currentStyle())
            style_manager.setCurrentStyle(style)
            # print(style_manager.currentStyle())
            # print(layer.id())
            self.iface.layerTreeView().refreshLayerSymbology(layer.id())
    self.iface.mapCanvas().refreshAllLayers()
0

Thanks to looking at your source code etrimaille , I managed to figure it out. Now it's both more concise and it works the way I want it to.

Before the Layer Legend never got updated with the newly loaded style set. Now it does.

    def populate_layer_styles(self, layer):
    styles = layer.listStylesInDatabase()
    for style_id, style_name in zip(styles[1], styles[2]):
        style_set = style_name[3]
        if not style_set in self.layer_styles:
            self.layer_styles[style_set] = {}
        style_layer = style_name[5:]
        lyr = QgsProject.instance().mapLayersByName(style_layer)[0]
        style_manager = lyr.styleManager()
        style_xml = QgsMapLayerStyle(lyr.getStyleFromDatabase(style_id)[0])
        style_manager.addStyle(style_name, style_xml)
        self.layer_styles[style_set][style_layer] = style_manager

    # Update radio buttons on the interface
    for style_set in self.layer_styles:
        if style_set not in self.dockwidget.rb_styles.button:
            self.dockwidget.rb_styles.add_button(str(style_set), self.activate_style_set)

def activate_style_set(self, style_set):
    # print(style_set.text())
    for layer in QgsProject.instance().mapLayers().values():
        # only for vector layers
        if layer.type() != 0:
            continue
        # Extract table name using light regex
        table_re = re.compile(r'table="main"."(mow_.+?)"')
        table = table_re.search(layer.source())
        if table:
            table = table.group(1)
        else:
            continue
        # And compare it with layer names used for reloading the layers
        if table in self.tablenames:
            style = 'set' + style_set.text() + '-' + table
            style_manager = self.layer_styles[style_set.text()][style[5:]]
            style_manager.setCurrentStyle(style)
            self.iface.layerTreeView().refreshLayerSymbology(layer.id())
    self.iface.mapCanvas().refreshAllLayers()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.