2

I'm building an Atlas generator based on the QGIS Layout engine. I want to fetch extra data from database based on some attributes of the current feature.

In PyQGIS 2, that was possible with

for i in range(0, atlas.numFeatures()):
   atlas.prepareForFeature(i)
   feat = atlas.feature()  # here I can access any attribute of the current feature  

I can't find a way to get the same in PyQGIS 3...

atlas = self.layout.atlas()
atlas.setEnabled(True)
atlas.setCoverageLayer(self.loaded_layers_registry['campaign_parcel'])
atlas.setFilterFeatures(True)
atlas.setFilterExpression('Some expression')

atlas.beginRender()
for i in range(0, atlas.count()):
    feature = atlas.??????  # How to get the current feature here????
    current_client = results(GET_CLIENT, [feature['client_id']])
    # Here I want to update the text of a label or whatever....
    exporter = QgsLayoutExporter(atlas.layout())
    exporter.exportToPdf(os.path.join(export_directory, atlas.currentFilename() + '.pdf'), QgsLayoutExporter.PdfExportSettings())
    atlas.next()

atlas.endRender()

Does anyone know a way?

-- EDIT --

As @J.Monticolo says in comments there is a way but it seems very ugly to me...

The idea is to use the featureChanged signal of QgsLayoutAtlas to catch the actual feature. It seems to works pretty well but you need to manually starts the iteration to force the signal to be fired for the first feature. Then you need to come back to the start. This is the part that seems bad to me...

This is what I use for the moment:

def on_atlas_feature_changed(self, feat):
    self.atlas_feature = feat

def export_cr(self):
    atlas = self.layout.atlas()
    atlas.featureChanged.connect(self.on_atlas_feature_changed)
    atlas.beginRender()
    for i in range(0, atlas.count()):
        if not self.atlas_feature: # self.atlas_feature is initialize to None 
            atlas.next()
            atlas.first() # this step is necessary to fire the featureChanged() signal that give the actual QgsFeature
        epandage_html = self.get_epandage_html(self.atlas_feature['id'])
        txt_epandage = self.layout.itemById('epandage')
        txt_epandage.setText(self.get_epandage_html(self.atlas_feature['id']))
        exporter = QgsLayoutExporter(atlas.layout())
        exporter.exportToPdf(os.path.join(export_directory, atlas.currentFilename() + '.pdf'), QgsLayoutExporter.PdfExportSettings())
        atlas.next()

    atlas.endRender()
    atlas.featureChanged.disconnect(self.on_atlas_feature_changed)
    self.atlas_feature = None
8
  • Have you tried something like atlas.coverageLayer().getFeature(atlas.currentFeatureNumber()) ? Commented Dec 14, 2021 at 13:23
  • Nope, but from my understanding the currentFeatureNumber is more like a counter in the atlas and not the actual id of a feature. I will make a try and let you know, thk's for your comment.
    – YoLecomte
    Commented Dec 15, 2021 at 7:18
  • 1
    @J.Monticolo As I expect, the currentFeatureNumber() returns the feature number in the atlas (0, then 1, then 2,...) but the not the feature id.
    – YoLecomte
    Commented Dec 16, 2021 at 8:30
  • so, maybe playing with the signal featureChanged and catch the current feature, I've no other idea. Commented Dec 16, 2021 at 8:37
  • Nice idea! Actually it almost works. But as the signal is only fired when feature changed, I miss the first one...
    – YoLecomte
    Commented Dec 16, 2021 at 8:54

1 Answer 1

3

So I reviewed a little your script and below a proposed code :


class MyPyQgisScript(QObject):

    def __init__(self, project_layout: QgsLayout, parent=None):
        super(MyPyQgisScript, self).__init__(parent)
        self._atlas_feature = None
        self.layout = project_layout
        self.atlas = self.layout.atlas()
        # connect the signal to catch the atlas feature
        self.atlas.featureChanged.connect(
            lambda feat: setattr(self.atlas_feature, feat)
        )
        # init atlas_feature
        self.atlas.refreshCurrentFeature()

    @property
    def atlas_feature(self) -> QgsFeature:
        """Project layout Current Atlas feature"""
        return self._atlas_feature

    @atlas_feature.setter
    def atlas_feature(self, feat: QgsFeature) -> None:
        """Sets the atlas feature
        :param feat: the current atlas feature
        """
        if isinstance(feat, QgsFeature):
            self._atlas_feature = feat
        else:
            self._atlas_feature = None

    def export_cr(self) -> None:
        """Exports the atlas to pdf"""
        self.atlas.beginRender()
        for i in range(0, self.atlas.count()):
            epandage_html = self.get_epandage_html(self.atlas_feature["id"])
            txt_epandage = self.layout.itemById("epandage")
            txt_epandage.setText(
                self.get_epandage_html(self.atlas_feature["id"])
            )
            exporter = QgsLayoutExporter(self.atlas.layout())
            exporter.exportToPdf(
                os.path.join(
                    export_directory,
                    f"{self.atlas.currentFilename()}.pdf"),
                    QgsLayoutExporter.PdfExportSettings()
            )
            self.atlas.next()

        self.atlas.endRender()
1
  • Thk's for this, it works well and look cleaner!
    – YoLecomte
    Commented Dec 17, 2021 at 9:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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