הרצת סקריפטים (הכוונה שלי היא סקריפטים בפייתון) ב  Processing tool הינו תהליך של הרצת סקריפט שלא מובנה בתוכנה דרך הממשק של כלי המדף ומאפשר שימוש וחישוב על שכבות שנמצאים במפה ו/או נתונים נוספים.

ב Arcmap האפשרות של עבודה עם כלים מבוססי סקריפטים של פייתון היא מאוד פשוטה וישנם לא מעט דוגמאות והסברים מפורטים. גם השיטה של הגדרת הפרמטרים השונים היא מאוד ברורה (וגם אם לא חיפוש קצר בגוגל יביא לא מעט תוצאות) ויש הסבר ודוגמאות לכל פונקציה ב arcpy.

שמנסים להעביר את התהליך לQgis זה נהיה קצת יותר מורכב למי שרגיל לעבוד עם arcpy (לפחות מבחנתי) צריך לשים לב לכמה דברים.

יש שינוי מהותי בין Qgis 2 ל Qgis 3  מעבר לעובדה שגרסת הפייתון עוברת מ 2 ל 3 ישנם שינויים נוספים בצורת הגדרת הפרמטרים והגדרות נוספות כמו כן העבודה היא  PyQt5 ולא PyQt4

בפוסט הקרוב לא יהיה ממש מדריך על כותבים את הסקריפט ואיך מוספים את הסקריפטים ל Processing tool מקווה לכתוב מתישהו…

הוספת ספריית פייתון ל QGIS

על ספרייות/הרחבות פייתון כבר כתבתי בעבר,  מי שעובד קצת עם פייתון בטח שם לב שיש מצב שיש לו על המחשב כמה וכמה פייתונים..

יש פייתון שמותקן עם הArcMap  ויש פייתון שמותקן עם ה QGIS ויכול להיות שהתקנו באופן עצמאי או דרך Conda  ובכלל יש מצב שיש לנו כמה גרסאות למשל גם 2.7 וגם 3.6 קיצר בלאגן (אני לא אכנס לדרכים להתמודד עם הבעיה אבל רק כדאי שתשימו לב ותראו איזה פייתון אתם מריצים..)

משהו אחד שיכול להיות בעיה זה במידה והתקנתם הרחבות/ספריות בפייתון אחד (למשל דרך Jupyter) הוא לא בהכרח יהיה זמין במקומות אחרים.

לצורך העניין ב Jupyter עבדתי עם Pandas  וב Qgis הוא לא היה קיים, שברתי על זה קצת את הראש איך מתקנים ב Qgis  אז הנה הפתרון..

במידה ורוצים להוסיף ספריית פייתון לסקריפט שירוץ ב QGIS נפתח אתOSGeo4w Shell   (אמור להיות מותקן יחד עם הQGIS כדאי לוודא שה QGIS סגור ויכול להיות שצריך לפתוח כאדמין)

כדי לוודא שאנחנו עובדים על פייתון 3 פשוט נריץ

py3_env

ואח”כ נריץ את הפקודה של ההתקנה

python3 -m pip install --upgrade pandas

דוגמה לסקריפט שרץ דרך ה Processing Toolbox

כתבתי כבר פעם על GTFS ואיך ניתן לחלץ ממנו מידע מרחבי ולבצע חישובים שונים כמו למשל כיסוי התחבורה ציבורית ועוד.

בפוסט הקודם צרפתי סקריפט שמחזיר לכל תחנה את שמות הקווים שעוברים בו (route_short_name) ללא שייוך למפעיל..

אז הפעם הנה אותו הסקריפט רק שנפעיל אותו דרך Qgis

מזהיר מראש הסקריפט לא מושלם יש לי עדיין בעיה קטנה עם  ההגדרה של הוספת הפלט למפה אז אפשר להוסיף אותו עצמאית או לשפר את הסקריפט הנוכחי…

הקלט של הסקריפט הוא בעצם קובץ ה zip של נתוני GTFS (למשל של משרד התחבורה)

שלב ראשון הגדרות כלליות והספת הספריות הרלונטיות

#!/usr/bin/env python
# coding: utf-8

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessingOutputVectorLayer,QgsField, QgsFeature, QgsFeatureSink, QgsFeatureRequest, QgsProcessing,QgsProcessingParameterFile, QgsProcessingAlgorithm, QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink,QgsVectorLayer,QgsProject,QgsCoordinateReferenceSystem)
                      

from zipfile import ZipFile
import pandas as pd
import datetime
import csv
from collections import defaultdict
import json


שלב שני הגדרות שונות על מנת לעבוד דרך הממשק של QGIS והגדרות הקלט והפלט

class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):

OUTPUT = 'OUTPUT'
INPUT = 'INPUT'


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("Add stops with route short name")

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

def groupId(self):
return "GTFS"

def shortHelpString(self):
return self.tr("Example script without logic")

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 gtfs_zip file")
))

שלב שלישי הפונקציה עצמה (שימו לב הקובץ נשמר ב C:\temp:

	def processAlgorithm(self, parameters, context, feedback):
		self.addOutput(
		QgsProcessingOutputVectorLayer(
		self.OUTPUT,
		self.tr('Output'),
		type = QgsProcessing.TypeVectorAnyGeometry)
)
		zip_file = ZipFile(self.parameterAsString(parameters,self.INPUT,context))	
		
		routes=pd.read_csv(zip_file.open("routes.txt"))
		trips_file=pd.read_csv(zip_file.open("trips.txt"))
		st=pd.read_csv(zip_file.open("stop_times.txt"))
		stops_loc=pd.read_csv(zip_file.open("stops.txt"))
		#b = pd.read_csv(r"C:\Users\yehudah\Downloads\Atlanta gtfs\trips.txt")
		trips_file = trips_file.dropna(axis=1)
		merged = routes.merge(trips_file, on='route_id')


		df1 = merged[['route_id','trip_id']]

		route_dict={k: g["trip_id"].tolist() for k,g in df1.groupby("route_id")}

		tr_dict = {}
		for k, v in route_dict.items():
				for l in range(len(v)):
					tr_dict.setdefault(v[l], []).append(k)

		
		st1=st[['trip_id','stop_id']]
		trip_dict={k: g["stop_id"].tolist() for k,g in st1.groupby("trip_id")}
		rt1 = merged[['trip_id','route_short_name']]
		stop_dict = {}
		for k, v in trip_dict.items():
				for l in range(len(v)):
					stop_dict.setdefault(v[l], []).append(k)

		kav_dict={k: g["route_short_name"].tolist() for k,g in rt1.groupby("trip_id")}
		stops = stop_dict.keys()
		trips= trip_dict.keys()
		kav=  kav_dict.keys()
		stopsKav=defaultdict(list)
		def kavInStop(stopid):
			a=stop_dict[stopid]
			ll=[kav_dict[i] for i in a]
			fl=list(set([k for sl in ll for k in sl]))
			stopsKav[stopid].append(fl)
		for i  in stops:
			kavInStop(i)

		st2= st1.dropna(axis=1)
		merged2 = rt1.merge(st2, on='trip_id')

		stopsKav={k: g["route_short_name"].tolist() for k,g in merged2.groupby("stop_id")}

		for i  in stops:
			stopsKav[i]=list(set(stopsKav[i]))

		stops_loc["route_SN"]=""


		for index, row in  stops_loc.iterrows() :
			#print(i)|
			
			try:
				#row['route_SN'] =stopsKav[row['stop_id']]
				stops_loc.loc[index,'route_SN'] = str(stopsKav[row['stop_id']]).replace("]","").replace("[","").replace("'","")
				#print (stopsKav[row['stop_id']])
				
			except:
				pass
		out_loc=r"C:\temp\stops_test.csv"
		stops_loc.to_csv( out_loc, encoding='utf-8', index=False)
		
		
		uri= "file:///C:/temp/stops_test.csv?delimiter=,&crs=epsg:4326&yField=stop_lat&xField=stop_lon"
		layer = QgsVectorLayer(uri,'stops', 'delimitedtext')
		QgsProject.instance().addMapLayer(layer).autoRefreshInterval()
		
		
		results=processing.run({self.OUTPUT: layer})
		return results[self.OUTPUT]

הממשק בסוף נראה ככה

והתוצאה בסוף היא בעצם טבלת התחנות עם עמודה עם הקווים שעוברים בה

[elementor-template id="1652"]