2 votos

QGIS se bloquea en el plugin de procesamiento personalizado

Soy bastante nuevo en Python y QGIS pero estoy tratando de crear un algoritmo para un plugin de procesamiento que importará un CSV de IDs de Tweets. Luego los hidrata usando la librería Twarc de Python para obtener un CSV con metadatos extraídos de Twitter, y luego crear puntos a partir de sus coordenadas en el CSV. He llegado bastante lejos pero ahora he llegado a un muro.

Cuando ejecuto el script de mi plugin de procesamiento, QGIS se bloquea completamente y no da ningún mensaje de error. El último error que tuve fue un error que tiene que ver con QFileDialog así que sospecho que tiene que ver con eso, pero quitarlo sigue haciendo que QGIS se cuelgue.

Desgraciadamente, también forma parte de un proyecto importante para la universidad que debe presentarse dentro de dos días, y el personal docente no es demasiado servicial.

¿Tiene algún consejo específicamente con QGIS estrellarse debido a la secuencia de comandos, o en el código en general?

Estoy usando mac OS con QGIS 3.20 Odense.

Mi guión:

#importing the modules I think I need

from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import *

from twarc.client2 import Twarc2, expansions
import json
from twarc_csv import CSVConverter
from qgis.core import QgsCoordinateReferenceSystem
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QFileDialog

class HydrateGeoTweetsAlgorithm(QgsProcessingAlgorithm):

#processing boilerplate
    OUTPUT = 'OUTPUT'
    INPUT = 'INPUT'

    def initAlgorithm(self, config):

#importing csv of tweet IDs
        self.addParameter(
            QgsProcessingParameterFile(
                self.INPUT,
                self.tr('Input file')
            )
        )

#output point layer
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output layer')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):

#asking user to specify  a file to input
        qfd = QFileDialog()
        title = 'Open File'

        path = "/Users/"

        tweet_ids = QFileDialog.getOpenFileName(qfd, title, path)

#token you need to request from twitter API
        t = Twarc2(bearer_token="")

#iterating over csv using twarc to hydrate each tweet ID
        lookup = t.tweet_lookup(tweet_ids=tweet_ids)
        for page in lookup:
            result = expansions.flatten(page)
            with open("results.jsonl", "w+") as f:
                f.write(json.dumps(page) + "\n")

#converting jsonl to csv to get coordinates
        with open("results.jsonl", "r") as infile:
            with open("output.csv", "w") as outfile:
                convert = CSVConverter(infile, outfile, json_encode_all=False, json_encode_lists=True, json_encode_text=False, inline_referenced_tweets=True, allow_duplicates=False, batch_size=1000)
                convert.process()

#plotting points based on coordinates
        for tweet in convert:
            if tweet["geo.coordinates.coordinates"]:
                processing.run("native:createpointslayerfromtable",
                {'INPUT':'output.csv',
                'XFIELD':'cooridnates[1]',
                'YFIELD':'cooridnates[0]',
                'ZFIELD':'',
                'MFIELD':'',
                'TARGET_CRS':QgsCoordinateReferenceSystem(''),
                'OUTPUT':'\\Users\\tweet_points.shp'})

        return QgsMapLayerRegistry.instance().addMapLayers([tweet_points.shp])

#other processing boilerplate
    def name(self):

        return 'Hydrate and plot tweets'

    def displayName(self):

        return self.tr(self.name())

    def group(self):

        return self.tr(self.groupId())

    def groupId(self):

        return ''

    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return HydrateGeoTweetsAlgorithm()

2voto

Jeremy White Puntos 381

Esta es una pregunta un poco difícil de responder porque, aunque has explicado básicamente tu intención en el cuerpo de la pregunta, es casi un "muro de código", porque desafortunadamente, la lógica de hidratación del tweet no es fácilmente reproducible. Por mi parte, nunca he utilizado la librería twarc, y parece que se necesita un api token que hace que esto sea difícil de probar.

Sin embargo, voy a tratar de abordar los problemas en el lado de procesamiento de qgis, ya que tiene unos cuantos.

  1. No es necesario QFileDialog en todo QgsProcessingParameterFile se encarga de obtener el csv de entrada de los ids de los tweets. Además, puedes pasar un argumento de extensión para que sólo se seleccionen los archivos '.csv'.

  2. Para recuperar su archivo csv de entrada y almacenarlo en el tweet_ids variable, haces esto:

     tweet_ids = self.parameterAsFile(parameters, self.INPUT, 
     context)
  3. No creo que realmente quiera ejecutar el algoritmo hijo 'Create Points Layer From Table' iterativamente en un bucle for. Es difícil para mí decir con seguridad porque no sé exactamente lo que su csv parece, pero supongo que esto va a crear una capa de salida por separado para cada fila de tweets en su csv. En su lugar, sólo ejecútelo una vez y pase el archivo csv como entrada y las columnas que contienen los valores X e Y. Por cierto, los nombres de las columnas en tu script están mal escritos. cooridnates[1] y cooridnates[0] . ¿Es un error en tu script o un error en el csv?

  4. No codifique una ruta de archivo en el 'OUTPUT' del algoritmo hijo. También en este caso se encarga de ello el QgsProcessingParameterFeatureSink que ha añadido a su script en la sección initAlgorithm() método. Si sólo pasa parameters['OUTPUT'] Esto permite al usuario guardar un archivo en cualquier formato geoespacial o simplemente crear una capa de memoria temporal.

Además, esta línea:

return QgsMapLayerRegistry.instance().addMapLayers([tweet_points.shp])

se equivoca por varias razones. En primer lugar, QgsMapLayerRegistry fue sustituido por QgsProject en la API de QGIS 3. Probablemente lo hayas copiado de una respuesta antigua o de un tutorial escrito para QGIS 2. En segundo lugar, no tienes que preocuparte de cargar tu capa de salida en el proyecto. Tengo entendido que esto es manejado por QgsProcessingContext . Si la casilla "Abrir archivo de salida después de ejecutar el algoritmo" está marcada, la capa de salida se cargará al finalizar. Por último, todos los algoritmos de procesamiento de qgis deben devolver un objeto diccionario (incluso sólo uno vacío), por lo que puede hacer simplemente: return {} .

Sin embargo, si desea poder utilizar una capa de salida del algoritmo como entrada para un procesamiento posterior, por ejemplo en un modelo o algo así, puede devolver un diccionario con un self.OUTPUT y un valor de identificación de la capa de salida.

Por ejemplo return {self.OUTPUT: dest_id}

(véase el final de la secuencia de comandos a continuación para ver un ejemplo)

Y por último, todos los algoritmos de procesamiento se ejecutan en un hilo de fondo por defecto. Así que si haces algo que no es seguro para el hilo en el processAlgorithm() puede hacer que QGIS se bloquee. Para evitar esto puedes reimplementar un método flags() en tu clase de algoritmo y devolver la bandera No Threading, por ejemplo

def flags(self):
    return super().flags() | QgsProcessingAlgorithm.FlagNoThreading

A continuación se muestra un ejemplo de trabajo de un algoritmo que toma un archivo csv de entrada y ejecuta native:createpointslayerfromtable como algoritmo hijo para crear una capa de puntos de salida. Todavía tiene que asegurarse de que su twarc está funcionando porque realmente no puedo ayudarte con eso. Si tienes problemas con esa parte, te sugeriría que lo hicieras funcionar en la consola de QGIS Python, y luego lo pegaras y adaptaras en el script de procesamiento.

Esperemos que este ejemplo te ayude:

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsFeatureSink, QgsCoordinateReferenceSystem, QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterFile, QgsProcessingParameterFeatureSink)
from qgis import processing

class ExAlgo(QgsProcessingAlgorithm):
    INPUT = 'INPUT'
    OUTPUT = 'OUTPUT'

    def __init__(self):
        super().__init__()

    def name(self):
        return "exalgo"

    def tr(self, text):
        return QCoreApplication.translate("exalgo", text)

    def displayName(self):
        return self.tr("Example script")

    def group(self):
        return self.tr("Examples")

    def groupId(self):
        return "examples"

    def shortHelpString(self):
        return self.tr("Example script with an input csv file")

    def helpUrl(self):
        return "https://qgis.org"

    def createInstance(self):
        return type(self)()

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFile(
            self.INPUT,
            self.tr("Input table"),
            extension='csv'))

        self.addParameter(QgsProcessingParameterFeatureSink(
            self.OUTPUT,
            self.tr("Output layer"),
            QgsProcessing.TypeVectorPoint))

    def processAlgorithm(self, parameters, context, feedback):

        # retrieve your input file parameter
        source = self.parameterAsFile(parameters, self.INPUT, context)

        # This is where you hydrate your tweet ids etc, convert to
        # .json and back to a csv with coordinate columns...

        point_layer_result = processing.run("native:createpointslayerfromtable",
                {'INPUT': source, # instead of source, pass your converted csv file
                'XFIELD':'Longitude', # make sure these fields exist & names are correct
                'YFIELD':'Latitude',
                'ZFIELD':'',
                'MFIELD':'',
                'TARGET_CRS':QgsCoordinateReferenceSystem('EPSG:4326'), # pass the appropriate crs
                'OUTPUT':parameters['OUTPUT']},
                is_child_algorithm=True,
                context=context,
                feedback=feedback)

        dest_id = point_layer_result['OUTPUT']

        return {self.OUTPUT: dest_id} # all processing algs must return a dictionary object (can be empty)

Vea un breve screencast a continuación para ver los resultados de este script de ejemplo:

enter image description here

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X