Pisanje Pajton skripti za okvir za obradu (QGIS3)

Moguće je pisati samostalne pyqgis skripte koje se mogu pokrenuti preko Python konzole u QGIS-u. Uz nekoliko podešavanja, možete naterati svoje samostalne skripte da se pokreću preko Processing Framework-a. Ovo ima nekoliko prednosti. Prvo, uzimanje korisničkog unosa i pisanje izlaznih datoteka je mnogo lakše jer Processing Framework nudi standardizovani korisnički interfejs za njih. Drugo, prisustvo vaše skripte u Processing Toolbox-u joj takođe omogućava da bude deo bilo kog modela obrade ili da se pokreće kao paketni zadatak sa više unosa. Ovaj tutorijal će pokazati kako napisati prilagođenu python skriptu koja može biti deo Processing Framework-a u QGIS-u.

Белешка

API za obradu je potpuno redizajniran u QGIS3. Molimo pogledajte ovaj vodič za najbolje prakse i savete.

Pregled zadatka

Naš skript će izvršiti operaciju rastvoravanja na osnovu polja koje je korisnik izabrao. Takođe će sumirati vrednosti drugog polja za rastvorene karakteristike. U primeru, rastvorićemo svetski shapefile na osnovu atributa „CONTINENT“ i sumirati polje „POP_EST“ da bismo izračunali ukupnu populaciju u rastvorenom regionu.

Dobijte podatke

Koristićemo skup podataka Admin 0 - Countries iz Natural Earth-a.

Preuzmite datoteku Admin 0 - countries shapefile..

Izvor podataka [NATURALEARTH]

Radi lakšeg snalaženja, možete direktno preuzeti geopaket koji sadrži gornji sloj odozdo:

ne_global.gpkg

Procedura

  1. U QGIS panelu pregledača, pronađite direktorijum u koji ste sačuvali preuzete podatke. Proširite datoteku zip ili gpkg i izaberite sloj ne_10m_admin_0_countries. Prevucite sloj na platno.

../../_images/1146.png
  1. Idite na Obrada ‣ Alatke. Kliknite na dugme Skripte u traci sa alatkama i izaberite Kreiraj novu skriptu iz šablona.

../../_images/2125.png
  1. Šablon sadrži sav osnovni kod koji je potreban da bi ga Processing Framework prepoznao kao skriptu za obradu i upravljao ulazima/izlazima. Hajde da počnemo sa prilagođavanjem primernog šablona našim potrebama. Prvo promenite naziv klase iz ExampleProcessingAlgorithm u DissolveProcessingAlgorithm. Ovo ime takođe treba ažurirati u metodi createInstance. Dodajte dokumentacioni string klasi koji objašnjava šta algoritam radi.

../../_images/360.png
  1. Kako se pomerate nadole, videćete metode koje dodeljuju ime, grupu, opis itd. skripti. Promenite povratne vrednosti za metod name na dissolve_with_sum, metod displayName na Dissolve with Sum, metod group i metod groupId na scripts. Promenite povratnu vrednost metode shortHelpString na opis koji će se prikazati korisniku. Kliknite na dugme Sačuvaj.

../../_images/434.png
  1. Nazovite skriptu dissolve_with_sum i sačuvajte je na podrazumevanoj lokaciji u folderu profiles ‣ default ‣ processing ‣ scripts.

../../_images/530.png
  1. Sada ćemo definisati ulaze za skriptu. Šablon već sadrži definiciju vektorskog sloja „INPUT“ i sloja „OUTPUT“. Dodaćemo 2 nova ulaza koja omogućavaju korisniku da izabere „DISSOLVE_FIELD“ i „SUM_FIELD“. Dodajte novi uvoz na vrhu i sledeći kod u metodi „initAlgorithm“. Kliknite na dugme Run da biste pregledali promene.

from qgis.core import QgsProcessingParameterField

self.addParameter(
        QgsProcessingParameterField(
                self.DISSOLVE_FIELD,
                'Choose Dissolve Field',
                '',
                self.INPUT))

self.addParameter(
        QgsProcessingParameterField(
                self.SUM_FIELD,
                'Choose Sum Field',
                '',
                self.INPUT))
../../_images/6a.png ../../_images/6b.png
  1. Videćete dijalog Dissolve with Sum sa našim novodefinisanim ulazima. Izaberite sloj ne_10m_admin_0_countries kao Input layer`. Pošto su i Dissolve Field i Sum Fields filtrirani na osnovu ulaznog sloja, biće unapred popunjeni postojećim poljima iz ulaznog sloja. Kliknite na dugme Close.

../../_images/729.png
  1. Sada definišemo našu prilagođenu logiku za obradu podataka u metodi processAlgorithm. Ovoj metodi se prosleđuje rečnik pod nazivom parameters. On sadrži ulaze koje je korisnik izabrao. Postoje pomoćne metode koje vam omogućavaju da uzmete ove ulaze i kreirate odgovarajuće objekte. Prvo dobijamo naše ulaze koristeći metode parameterAsSource i parameterAsString. Zatim želimo da kreiramo stok karakteristika gde ćemo pisati izlaz. QGIS3 ima novu klasu pod nazivom QgsFeatureSink, što je preferirani način za kreiranje objekata koji mogu da prihvate nove karakteristike. Izlazu su potrebna samo 2 polja - jedno za vrednost rastvorenog polja i drugo za zbir izabranog polja.

from PyQt5.QtCore import QVariant
from qgis.core import QgsField, QgsFields

source = self.parameterAsSource(
        parameters,
        self.INPUT,
        context)
dissolve_field = self.parameterAsString(
        parameters,
        self.DISSOLVE_FIELD,
        context)
sum_field = self.parameterAsString(
        parameters,
        self.SUM_FIELD,
        context)

fields = QgsFields()
fields.append(QgsField(dissolve_field, QVariant.String))
fields.append(QgsField('SUM_' + sum_field, QVariant.Double))

(sink, dest_id) = self.parameterAsSink(
        parameters,
        self.OUTPUT,
        context, fields, source.wkbType(), source.sourceCrs())
../../_images/8a.png ../../_images/8b.png
  1. Sada ćemo pripremiti ulazne funkcije i kreirati rečnik koji će čuvati jedinstvene vrednosti iz polja za rastvaranje - dissolve_field i zbir vrednosti iz polja za sumu - sum_field. Obratite pažnju na upotrebu metode „feedback.pushInfo()“ za komunikaciju statusa sa korisnikom.

feedback.pushInfo('Extracting unique values from dissolve_field and computing sum')

features = source.getFeatures()
unique_values = set(f[dissolve_field] for f in features)

# Get Indices of dissolve field and sum field
dissolveIdx = source.fields().indexFromName(dissolve_field)
sumIdx = source.fields().indexFromName(sum_field)

# Find all unique values for the given dissolve_field and
# sum the corresponding values from the sum_field
sum_unique_values = {}
attrs = [{dissolve_field: f[dissolveIdx], sum_field: f[sumIdx]} for f in source.getFeatures()]
for unique_value in unique_values:
        val_list = [ f_attr[sum_field] for f_attr in attrs if f_attr[dissolve_field] == unique_value]
        sum_unique_values[unique_value] = sum(val_list)
../../_images/929.png
  1. Zatim ćemo pozvati ugrađeni algoritam za obradu native:dissolve na ulaznom sloju da bismo generisali rastvorene geometrije. Kada dobijemo rastvorene geometrije, iteriramo kroz izlaz algoritma za rastvorivanje i kreiramo nove karakteristike koje će biti dodate izlazu. Na kraju vraćamo dest_id FeatureSink kao izlaz. Sada je skripta spremna. Kliknite na dugme Run.

Белешка

Obratite pažnju na upotrebu „parameters[self.INPUT]“ za direktno preuzimanje ulaznog sloja iz rečnika parametara, bez definisanja istog kao izvora. Pošto prosleđujemo ulazni objekat algoritmu bez ikakve radnje sa njim, nije potrebno definisati ga kao izvor.

from qgis.core import QgsFeature

# Running the processing dissolve algorithm
feedback.pushInfo('Dissolving features')
dissolved_layer = processing.run("native:dissolve", {
        'INPUT': parameters[self.INPUT],
        'FIELD': dissolve_field,
        'OUTPUT': 'memory:'
        }, context=context, feedback=feedback)['OUTPUT']

# Read the dissolved layer and create output features
for f in dissolved_layer.getFeatures():
        new_feature =  QgsFeature()
        # Set geometry to dissolved geometry
        new_feature.setGeometry(f.geometry())
        # Set attributes from sum_unique_values dictionary that we had computed
        new_feature.setAttributes([f[dissolve_field], sum_unique_values[f[dissolve_field]]])
        sink.addFeature(new_feature, QgsFeatureSink.FastInsert)

return {self.OUTPUT: dest_id}
../../_images/10a.png ../../_images/10b.png
  1. U dijalogu Dissolve with Sum, izaberite ne_10m_admin_0_countries kao Ulazni sloj, CONTINENT kao Polje za rastvorivanje i POP_EST kao Polje za sumiranje. Kliknite na Pokreni.

../../_images/1147.png
  1. Kada je obrada završena, kliknite na dugme Zatvori i prebacite se na glavni QGIS prozor.

../../_images/1238.png
  1. Videćete rastvoreni izlazni sloj sa po jednom karakteristikom za svaki kontinent i ukupnom populacijom sabranom iz pojedinačnih zemalja koje pripadaju tom kontinentu.

../../_images/1336.png
  1. Još jedna prednost pisanja skripte za obradu je to što su metode unutar Processing Framework-a svesne izbora sloja i automatski filtriraju vaše ulaze da bi koristile samo odabrane karakteristike. To se dešava zato što definišemo naš ulaz kao „QgsProcessingParameterFeatureSource“. Izvor karakteristika omogućava korišćenje BILO KOJEG objekta koji sadrži vektorske karakteristike, ne samo vektorskog sloja, tako da kada postoje odabrane karakteristike u vašem sloju i zatražite od Processing-a da koristi odabrane karakteristike, ulaz se prosleđuje vašem skriptu kao objekat „QgsProcessingFeatureSource“ koji sadrži odabrane karakteristike, a ne ceo vektorski sloj. Evo kratke demonstracije ove funkcionalnosti. Recimo da želimo da rastvorimo samo određene kontinente. Hajde da napravimo izbor koristeći alatku Izaberi karakteristiku pomoću izraza.

../../_images/1433.png
  1. Unesite sledeći izraz da biste izabrali obeležja iz Severne i Južne Amerike i kliknite na Izaberi.

"CONTINENT" = 'North America' OR "CONTINENT" = 'South America'
../../_images/1529.png
  1. Videćete izabrane karakteristike označene žutom bojom. Pronađite skriptu „dissolve_with_sum“ i dvaput kliknite na nju da biste je pokrenuli.

../../_images/1627.png
  1. U dijalogu Dissolve with Sum, izaberite ne_10m_admin_0_countries kao Input layer. Ovaj put, obavezno označite polje Samo selected features. Izaberite SUBREGION kao Dissolve field i POP_EST kao Sum field.

../../_images/1729.png
  1. Kada se obrada završi, kliknite na Zatvori i vratite se na glavni QGIS prozor. Primetićete novi sloj sa samo odabranim objekatima rastvorenim. Kliknite na dugme Identifikuj i kliknite na objekat da biste proverili i potvrdili da je skripta ispravno radila.

../../_images/1825.png

Ispod je kompletan skript za referencu. Možete ga izmeniti prema svojim potrebama.

# -*- coding: utf-8 -*-

"""
***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************
"""

from PyQt5.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsFeature,
                       QgsField,
                       QgsFields,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterField,
                       )
import processing


class DissolveProcessingAlgorithm(QgsProcessingAlgorithm):
    """
    Dissolve algorithm that dissolves features based on selected
    attribute and summarizes the selected field by cumputing the
    sum of dissolved features.
    """
    INPUT = 'INPUT'
    OUTPUT = 'OUTPUT'
    DISSOLVE_FIELD = 'dissolve_field'
    SUM_FIELD = 'sum_field'

    def tr(self, string):
        """
        Returns a translatable string with the self.tr() function.
        """
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return DissolveProcessingAlgorithm()

    def name(self):
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'dissolve_with_sum'

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr('Dissolve with Sum')

    def group(self):
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return self.tr('scripts')

    def groupId(self):
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'scripts'

    def shortHelpString(self):
        """
        Returns a localised short helper string for the algorithm. This string
        should provide a basic description about what the algorithm does and the
        parameters and outputs associated with it..
        """
        return self.tr("Dissolves selected features and creates and sums values of features that were dissolved")

    def initAlgorithm(self, config=None):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
        # We add the input vector features source. It can have any kind of
        # geometry.
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                self.tr('Input layer'),
                [QgsProcessing.TypeVectorAnyGeometry]
            )
        )
        self.addParameter(
            QgsProcessingParameterField(
                self.DISSOLVE_FIELD,
                'Choose Dissolve Field',
                '',
                self.INPUT))
        self.addParameter(
            QgsProcessingParameterField(
                self.SUM_FIELD,
                'Choose Sum Field',
                '',
                self.INPUT))
        # We add a feature sink in which to store our processed features (this
        # usually takes the form of a newly created vector layer when the
        # algorithm is run in QGIS).
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output layer')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        source = self.parameterAsSource(
            parameters,
            self.INPUT,
            context
        )
        dissolve_field = self.parameterAsString(
            parameters,
            self.DISSOLVE_FIELD,
            context)
        sum_field = self.parameterAsString(
            parameters,
            self.SUM_FIELD,
            context)
        
        fields = QgsFields()
        fields.append(QgsField(dissolve_field, QVariant.String))
        fields.append(QgsField('SUM_' + sum_field, QVariant.Double))
        
        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context, fields, source.wkbType(), source.sourceCrs())
        
        # Create a dictionary to hold the unique values from the 
        # dissolve_field and the sum of the values from the sum_field
        feedback.pushInfo('Extracting unique values from dissolve_field and computing sum')
        features = source.getFeatures()
        unique_values = set(f[dissolve_field] for f in features)
        # Get Indices of dissolve field and sum field
        dissolveIdx = source.fields().indexFromName(dissolve_field)
        sumIdx = source.fields().indexFromName(sum_field)
        
        # Find all unique values for the given dissolve_field and
        # sum the corresponding values from the sum_field
        sum_unique_values = {}
        attrs = [{dissolve_field: f[dissolveIdx], sum_field: f[sumIdx]}
                for f in source.getFeatures()]
        for unique_value in unique_values:
            val_list = [ f_attr[sum_field] 
                for f_attr in attrs if f_attr[dissolve_field] == unique_value]
            sum_unique_values[unique_value] = sum(val_list)
        
        # Running the processing dissolve algorithm
        feedback.pushInfo('Dissolving features')
        dissolved_layer = processing.run("native:dissolve", {
            'INPUT': parameters[self.INPUT],
            'FIELD': dissolve_field,
            'OUTPUT': 'memory:'
        }, context=context, feedback=feedback)['OUTPUT']
        
        # Read the dissolved layer and create output features
        for f in dissolved_layer.getFeatures():
            new_feature =  QgsFeature()
            # Set geometry to dissolved geometry
            new_feature.setGeometry(f.geometry())
            # Set attributes from sum_unique_values dictionary that we had computed
            new_feature.setAttributes([f[dissolve_field], sum_unique_values[f[dissolve_field]]])
            sink.addFeature(new_feature, QgsFeatureSink.FastInsert)
        
        return {self.OUTPUT: dest_id}

If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)