23.9. كتابة خوارزميات معالجة جديدة كسكربتات Python

يوجد خياران لكتابة خوارزميات المعالجة باستخدام Python.

داخل QGIS، يمكنك استخدام Create new script في القائمة Scripts أعلى Processing Toolbox لفتح Processing Script Editor حيث يمكنك كتابة شيفرتك. ولتبسيط المهمة، يمكنك البدء بقالب سكربت باستخدام Create new script from template من القائمة نفسها. يفتح هذا قالبًا يوسّع الصنف QgsProcessingAlgorithm.

إذا حفظت السكربت في المجلد scripts (الموقع الافتراضي) بالامتداد .py، فستصبح الخوارزمية متاحة في Processing Toolbox.

23.9.1. توسيع QgsProcessingAlgorithm

الشيفرة التالية

  1. تأخذ طبقة متجهية كمُدخل

  2. تحسب عدد المعالم

  3. تُجري عملية نطاق عازل

  4. تنشئ طبقة شبكية من نتيجة عملية النطاق العازل

  5. تُعيد طبقة النطاق العازل والطبقة الشبكية وعدد المعالم

  1from qgis.PyQt.QtCore import QCoreApplication
  2from qgis.core import (QgsProcessing,
  3                       QgsProcessingAlgorithm,
  4                       QgsProcessingException,
  5                       QgsProcessingOutputNumber,
  6                       QgsProcessingParameterDistance,
  7                       QgsProcessingParameterFeatureSource,
  8                       QgsProcessingParameterVectorDestination,
  9                       QgsProcessingParameterRasterDestination)
 10from qgis import processing
 11
 12
 13class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):
 14    """
 15    This is an example algorithm that takes a vector layer,
 16    creates some new layers and returns some results.
 17    """
 18
 19    def tr(self, string):
 20        """
 21        Returns a translatable string with the self.tr() function.
 22        """
 23        return QCoreApplication.translate('Processing', string)
 24
 25    def createInstance(self):
 26        # Must return a new copy of your algorithm.
 27        return ExampleProcessingAlgorithm()
 28
 29    def name(self):
 30        """
 31        Returns the unique algorithm name.
 32        """
 33        return 'bufferrasterextend'
 34
 35    def displayName(self):
 36        """
 37        Returns the translated algorithm name.
 38        """
 39        return self.tr('Buffer and export to raster (extend)')
 40
 41    def group(self):
 42        """
 43        Returns the name of the group this algorithm belongs to.
 44        """
 45        return self.tr('Example scripts')
 46
 47    def groupId(self):
 48        """
 49        Returns the unique ID of the group this algorithm belongs
 50        to.
 51        """
 52        return 'examplescripts'
 53
 54    def shortHelpString(self):
 55        """
 56        Returns a localised short help string for the algorithm.
 57        """
 58        return self.tr('Example algorithm short description')
 59
 60    def initAlgorithm(self, config=None):
 61        """
 62        Here we define the inputs and outputs of the algorithm.
 63        """
 64        # 'INPUT' is the recommended name for the main input
 65        # parameter.
 66        self.addParameter(
 67            QgsProcessingParameterFeatureSource(
 68                'INPUT',
 69                self.tr('Input vector layer'),
 70                types=[QgsProcessing.TypeVectorAnyGeometry]
 71            )
 72        )
 73        self.addParameter(
 74            QgsProcessingParameterVectorDestination(
 75                'BUFFER_OUTPUT',
 76                self.tr('Buffer output'),
 77            )
 78        )
 79        # 'OUTPUT' is the recommended name for the main output
 80        # parameter.
 81        self.addParameter(
 82            QgsProcessingParameterRasterDestination(
 83                'OUTPUT',
 84                self.tr('Raster output')
 85            )
 86        )
 87        self.addParameter(
 88            QgsProcessingParameterDistance(
 89                'BUFFERDIST',
 90                self.tr('BUFFERDIST'),
 91                defaultValue = 1.0,
 92                # Make distance units match the INPUT layer units:
 93                parentParameterName='INPUT'
 94            )
 95        )
 96        self.addParameter(
 97            QgsProcessingParameterDistance(
 98                'CELLSIZE',
 99                self.tr('CELLSIZE'),
100                defaultValue = 10.0,
101                parentParameterName='INPUT'
102            )
103        )
104        self.addOutput(
105            QgsProcessingOutputNumber(
106                'NUMBEROFFEATURES',
107                self.tr('Number of features processed')
108            )
109        )
110
111    def processAlgorithm(self, parameters, context, feedback):
112        """
113        Here is where the processing itself takes place.
114        """
115        # First, we get the count of features from the INPUT layer.
116        # This layer is defined as a QgsProcessingParameterFeatureSource
117        # parameter, so it is retrieved by calling
118        # self.parameterAsSource.
119        input_featuresource = self.parameterAsSource(parameters,
120                                                     'INPUT',
121                                                     context)
122        numfeatures = input_featuresource.featureCount()
123
124        # Retrieve the buffer distance and raster cell size numeric
125        # values. Since these are numeric values, they are retrieved
126        # using self.parameterAsDouble.
127        bufferdist = self.parameterAsDouble(parameters, 'BUFFERDIST',
128                                            context)
129        rastercellsize = self.parameterAsDouble(parameters, 'CELLSIZE',
130                                                context)
131        if feedback.isCanceled():
132            return {}
133        buffer_result = processing.run(
134            'native:buffer',
135            {
136                # Here we pass on the original parameter values of INPUT
137                # and BUFFER_OUTPUT to the buffer algorithm.
138                'INPUT': parameters['INPUT'],
139                'OUTPUT': parameters['BUFFER_OUTPUT'],
140                'DISTANCE': bufferdist,
141                'SEGMENTS': 10,
142                'DISSOLVE': True,
143                'END_CAP_STYLE': 0,
144                'JOIN_STYLE': 0,
145                'MITER_LIMIT': 10
146            },
147            # Because the buffer algorithm is being run as a step in
148            # another larger algorithm, the is_child_algorithm option
149            # should be set to True
150            is_child_algorithm=True,
151            #
152            # It's important to pass on the context and feedback objects to
153            # child algorithms, so that they can properly give feedback to
154            # users and handle cancellation requests.
155            context=context,
156            feedback=feedback)
157
158        # Check for cancellation
159        if feedback.isCanceled():
160            return {}
161
162        # Run the separate rasterization algorithm using the buffer result
163        # as an input.
164        rasterized_result = processing.run(
165            'qgis:rasterize',
166            {
167                # Here we pass the 'OUTPUT' value from the buffer's result
168                # dictionary off to the rasterize child algorithm.
169                'LAYER': buffer_result['OUTPUT'],
170                'EXTENT': buffer_result['OUTPUT'],
171                'MAP_UNITS_PER_PIXEL': rastercellsize,
172                # Use the original parameter value.
173                'OUTPUT': parameters['OUTPUT']
174            },
175            is_child_algorithm=True,
176            context=context,
177            feedback=feedback)
178
179        if feedback.isCanceled():
180            return {}
181
182        # Return the results
183        return {'OUTPUT': rasterized_result['OUTPUT'],
184                'BUFFER_OUTPUT': buffer_result['OUTPUT'],
185                'NUMBEROFFEATURES': numfeatures}

الدوال القياسية لخوارزمية المعالجة:

  • createInstance (إلزامية)

    يجب أن تُعيد نسخة جديدة من خوارزميتك. إذا غيّرت اسم الصنف، فتأكد من تحديث القيمة المُعادة هنا أيضًا لتطابقه!

  • name (إلزامية)

    تُعيد الاسم الفريد للخوارزمية، المستخدم لتعريفها.

  • displayName (إلزامية)

    تُعيد اسم الخوارزمية المترجَم.

  • group

    تُعيد اسم المجموعة التي تنتمي إليها هذه الخوارزمية.

  • groupId

    تُعيد المعرّف الفريد للمجموعة التي تنتمي إليها هذه الخوارزمية.

  • shortHelpString

    تُعيد نص مساعدة قصيرًا ومترجَمًا محليًا للخوارزمية.

  • initAlgorithm (إلزامية)

    هنا نحدد مدخلات الخوارزمية ومخرجاتها.

    INPUT و OUTPUT هما الاسمان المُوصى بهما لمعامل الإدخال الرئيسي ومعامل الإخراج الرئيسي على التوالي.

    إذا كان معامل يعتمد على معامل آخر، تُستخدم parentParameterName لتحديد هذه العلاقة (قد يكون حقل طبقة أو نطاقها، أو وحدات المسافة لطبقة).

  • processAlgorithm (إلزامية)

    هنا تجري المعالجة.

    تُسترجع المعاملات باستخدام دوال مخصصة لهذا الغرض، مثل parameterAsSource و parameterAsDouble.

    يمكن استخدام processing.run لتشغيل خوارزميات معالجة أخرى من داخل خوارزمية معالجة. المعامل الأول هو اسم الخوارزمية، والثاني قاموس بمعاملات الخوارزمية. يُضبط is_child_algorithm عادةً على True عند تشغيل خوارزمية من داخل خوارزمية أخرى. يُعلِم context و feedback الخوارزمية بالبيئة التي ستعمل فيها وبقناة التواصل مع المستخدم (التقاط طلب الإلغاء، والإبلاغ عن التقدم، وتقديم ملاحظات نصية). عند استخدام معاملات الخوارزمية (الأصل) كمعاملات للخوارزميات "الفرعية"، يجب استخدام قيم المعاملات الأصلية (مثل parameters['OUTPUT']).

    من الممارسات الجيدة التحقق من كائن feedback بحثًا عن طلب إلغاء بقدر ما يكون ذلك معقولًا! فهذا يتيح استجابة سريعة للإلغاء، بدلًا من إجبار المستخدمين على انتظار معالجة غير مرغوب فيها.

    يجب أن تُعيد الخوارزمية قيمًا لجميع معاملات المخرجات التي عرّفتها في صورة قاموس. في هذه الحالة، هي طبقتا مخرجات النطاق العازل والتحويل إلى شبكي، وعدد المعالم المعالَجة. يجب أن تطابق مفاتيح القاموس أسماء المعاملات/المخرجات الأصلية.

23.9.2. المُزخرِف ‎@alg

باستخدام المُزخرِف ‎@alg، يمكنك إنشاء خوارزمياتك الخاصة بكتابة شيفرة Python وإضافة بضعة أسطر إضافية لتوفير المعلومات اللازمة لجعلها خوارزمية معالجة سليمة. وهذا يبسّط إنشاء الخوارزميات وتحديد المدخلات والمخرجات.

من القيود المهمة لأسلوب المُزخرِف أن الخوارزميات المنشأة بهذه الطريقة ستُضاف دائمًا إلى مزوّد Processing Scripts الخاص بالمستخدم -- ولا يمكن إضافتها إلى مزوّد مخصص، مثلًا لاستخدامها في الإضافات.

تستخدم الشيفرة التالية المُزخرِف ‎@alg من أجل

  1. استخدام طبقة متجهية كمُدخل

  2. حساب عدد المعالم

  3. إجراء عملية نطاق عازل

  4. إنشاء طبقة شبكية من نتيجة عملية النطاق العازل

  5. تُعيد طبقة النطاق العازل والطبقة الشبكية وعدد المعالم

 1from qgis import processing
 2from qgis.processing import alg
 3from qgis.core import QgsProject
 4
 5@alg(name='bufferrasteralg', label='Buffer and export to raster (alg)',
 6     group='examplescripts', group_label='Example scripts')
 7# 'INPUT' is the recommended name for the main input parameter
 8@alg.input(type=alg.SOURCE, name='INPUT', label='Input vector layer')
 9# 'OUTPUT' is the recommended name for the main output parameter
10@alg.input(type=alg.RASTER_LAYER_DEST, name='OUTPUT',
11           label='Raster output')
12@alg.input(type=alg.VECTOR_LAYER_DEST, name='BUFFER_OUTPUT',
13           label='Buffer output')
14@alg.input(type=alg.DISTANCE, name='BUFFERDIST', label='BUFFER DISTANCE',
15           default=1.0)
16@alg.input(type=alg.DISTANCE, name='CELLSIZE', label='RASTER CELL SIZE',
17           default=10.0)
18@alg.output(type=alg.NUMBER, name='NUMBEROFFEATURES',
19            label='Number of features processed')
20
21def bufferrasteralg(instance, parameters, context, feedback, inputs):
22    """
23    Description of the algorithm.
24    (If there is no comment here, you will get an error)
25    """
26    input_featuresource = instance.parameterAsSource(parameters,
27                                                     'INPUT', context)
28    numfeatures = input_featuresource.featureCount()
29    bufferdist = instance.parameterAsDouble(parameters, 'BUFFERDIST',
30                                            context)
31    rastercellsize = instance.parameterAsDouble(parameters, 'CELLSIZE',
32                                                context)
33    if feedback.isCanceled():
34        return {}
35    buffer_result = processing.run('native:buffer',
36                               {'INPUT': parameters['INPUT'],
37                                'OUTPUT': parameters['BUFFER_OUTPUT'],
38                                'DISTANCE': bufferdist,
39                                'SEGMENTS': 10,
40                                'DISSOLVE': True,
41                                'END_CAP_STYLE': 0,
42                                'JOIN_STYLE': 0,
43                                'MITER_LIMIT': 10
44                                },
45                               is_child_algorithm=True,
46                               context=context,
47                               feedback=feedback)
48    if feedback.isCanceled():
49        return {}
50    rasterized_result = processing.run('qgis:rasterize',
51                               {'LAYER': buffer_result['OUTPUT'],
52                                'EXTENT': buffer_result['OUTPUT'],
53                                'MAP_UNITS_PER_PIXEL': rastercellsize,
54                                'OUTPUT': parameters['OUTPUT']
55                               },
56                               is_child_algorithm=True, context=context,
57                               feedback=feedback)
58    if feedback.isCanceled():
59        return {}
60    return {'OUTPUT': rasterized_result['OUTPUT'],
61            'BUFFER_OUTPUT': buffer_result['OUTPUT'],
62            'NUMBEROFFEATURES': numfeatures}

كما ترى، تتضمن الشيفرة خوارزميتين ('native:buffer' و 'qgis:rasterize'). الأخيرة ('qgis:rasterize') تنشئ طبقة شبكية من طبقة النطاق العازل التي ولّدتها الأولى ('native:buffer').

ليس من الصعب فهم الجزء من الشيفرة الذي تجري فيه هذه المعالجة إن كنت قد قرأت الفصل السابق. أما الأسطر الأولى فتحتاج إلى شرح إضافي؛ إذ توفّر المعلومات اللازمة لتحويل شيفرتك إلى خوارزمية يمكن تشغيلها من أي من مكوّنات الواجهة الرسومية، مثل صندوق الأدوات أو مصمّم النماذج.

هذه الأسطر كلها استدعاءات لدوال المُزخرِف @alg التي تساعد على تبسيط كتابة شيفرة الخوارزمية.

  • يُستخدم المُزخرِف ‎@alg لتحديد اسم الخوارزمية وموقعها في صندوق الأدوات.

  • يُستخدم المُزخرِف ‎@alg.input لتحديد مدخلات الخوارزمية.

  • يُستخدم المُزخرِف ‎@alg.output لتحديد مخرجات الخوارزمية.

للاطلاع على المعاملات الموجودة وما يقابلها، اقرأ أنواع المدخلات والمخرجات لخوارزميات المعالجة.

23.9.3. التعامل مع مخرجات الخوارزمية

عندما تعرّف مُخرجًا يمثّل طبقة (شبكية أو متجهية)، ستحاول الخوارزمية إضافته إلى QGIS عند انتهائها.

  • مخرجات طبقة شبكية: QgsProcessingParameterRasterDestination / alg.RASTER_LAYER_DEST.

  • مخرجات طبقة متجهية: QgsProcessingParameterVectorDestination / alg.VECTOR_LAYER_DEST.

لذلك، حتى إن كانت الدالة processing.run() لا تضيف الطبقات التي تنشئها إلى المشروع الحالي للمستخدم، فستُحمَّل طبقتا المخرجات (النطاق العازل والنطاق العازل الشبكي)، لأنهما محفوظتان في الوجهات التي أدخلها المستخدم (أو في وجهات مؤقتة إذا لم يحدد المستخدم وجهات).

إذا أُنشئت طبقة كمُخرج لخوارزمية، فيجب تعريفها على هذا النحو. وإلا فلن تتمكن من استخدام الخوارزمية بشكل سليم في مصمّم النماذج، لأن ما هو معرَّف لن يطابق ما تنشئه الخوارزمية فعلًا.

يمكنك إعادة سلاسل نصية وأرقام وغير ذلك بتحديدها في قاموس النتائج (كما هو موضّح لـ "NUMBEROFFEATURES")، لكن يجب دائمًا تعريفها صراحةً كمخرجات لخوارزميتك. ونشجّع على أن تُخرج الخوارزميات أكبر عدد ممكن من القيم المفيدة، لأنها قد تكون قيّمة للاستخدام في خوارزميات لاحقة عندما تُستخدم خوارزميتك كجزء من نموذج.

23.9.4. التواصل مع المستخدم

إذا كانت خوارزميتك تستغرق وقتًا طويلًا في المعالجة، فمن الجيد إعلام المستخدم بالتقدم. يمكنك استخدام feedback (QgsProcessingFeedback) لهذا الغرض.

يمكن تحديث نص التقدم وشريط التقدم باستخدام دالتين: setProgressText(text) و setProgress(percent).

يمكنك تقديم مزيد من المعلومات باستخدام pushCommandInfo(text) و pushDebugInfo(text) و pushInfo(text) و reportError(text).

إذا واجه سكربتك مشكلة، فالطريقة الصحيحة للتعامل معها هي إطلاق استثناء QgsProcessingException. يمكنك تمرير رسالة كوسيط إلى مُنشئ الاستثناء. وستتولى المعالجة التعامل معه والتواصل مع المستخدم، بحسب المكان الذي تُنفَّذ منه الخوارزمية (صندوق الأدوات، مصمّم النماذج، وحدة تحكم Python، ...)

23.9.5. توثيق السكربتات

يمكنك توثيق سكربتاتك بإعادة تعريف الدالتين helpString() و helpUrl() في الصنف QgsProcessingAlgorithm.

23.9.6. الأعلام (Flags)

يمكنك إعادة تعريف الدالة flags() في الصنف QgsProcessingAlgorithm لإخبار QGIS بالمزيد عن خوارزميتك. يمكنك مثلًا إخبار QGIS بأن السكربت يجب أن يُخفى عن مصمّم النماذج، أو أنه قابل للإلغاء، أو أنه غير آمن للعمل في خيوط متعددة، وغير ذلك.

نصيحة

افتراضيًا، تشغّل المعالجة الخوارزميات في خيط (thread) منفصل للحفاظ على استجابة QGIS أثناء تنفيذ مهمة المعالجة. إذا كانت خوارزميتك تتعطل بانتظام، فمن المحتمل أنك تستخدم استدعاءات API غير آمنة في خيط خلفي. جرّب إعادة العلم QgsProcessingAlgorithm.FlagNoThreading من الدالة flags() في خوارزميتك لإجبار المعالجة على تشغيل خوارزميتك في الخيط الرئيسي بدلًا من ذلك.

23.9.7. أفضل الممارسات لكتابة خوارزميات السكربتات

إليك ملخصًا سريعًا لأفكار ينبغي مراعاتها عند إنشاء خوارزميات السكربتات، خاصةً إذا أردت مشاركتها مع مستخدمي QGIS الآخرين. يضمن اتباع هذه القواعد البسيطة الاتساق عبر عناصر المعالجة المختلفة، مثل صندوق الأدوات ومصمّم النماذج وواجهة المعالجة الدفعية.

  • لا تحمّل الطبقات الناتجة. دع المعالجة تتولى نتائجك وتحمّل طبقاتك إذا لزم الأمر.

  • عرّف دائمًا المخرجات التي تنشئها خوارزميتك.

  • لا تعرض مربعات رسائل ولا تستخدم أي عنصر من الواجهة الرسومية في السكربت. إذا أردت التواصل مع المستخدم، فاستخدم دوال كائن feedback (QgsProcessingFeedback) أو أطلق استثناء QgsProcessingException.

تتوفر بالفعل خوارزميات معالجة كثيرة في QGIS. يمكنك العثور على الشيفرة في مستودع QGIS.