Writing custom toolboxes

Writing toolboxes will allow you to automate several tasks and make that code available to users as a GUI, or to other developers as an algorithm that can be executed using processing. In this section, you will learn how to create a toolbox and call it from processing.

In this chapter, you have learned how to load data from a file and from a PostGIS database. In this example, you will learn how to bring data in to QGIS from the SeeClickFix Application Program Interface (API).

SeeClickFix is a 311 reporting system that is used by many cities in the United States. It contains geospatial data and has a very well documented, and user-friendly API.

To create a new script, open the processing toolbox in QGIS. This will open an editor window. You will write your code in this window and save it using the save icon. The file name will become a toolbox under Tools|User scripts|File name. Save the file and name it SeeClickFix.

Now that you have an empty toolbox, we can start adding code. Before the code, you need to create the parameters you will want to pass to this algorithm. Each parameter will also become a GUI widget with the parameter name as the label. The SeeClickFix API allows you to specify a city or neighborhood and also filter strings. The following code will add these as parameters to our algorithm:

##City_or_Neighborhood= string City
##Filter=string Nothing
##Output=output vector

The previous code uses double comment symbols (##), then the parameter name followed by the parameter type and a default value. Default values are required for numbers and strings. The first parameter in the code is the city or neighborhood, it is a string and defaults to Albuquerque. Next, is the filter keyword, which is also a string and defaults to Nothing. Lastly, the code has an output, which is a type of output vector. The output will be what is added to the map or saved to disk. 

At this point, you can run the toolbox in the GUI and you will see the window shown in the following screenshot:

The GUI for the toolbox. Notice each parameter is a label

Next, you can import the libraries you need to perform the task. The following code will import what you need for the SeeClickFix toolbox:

import requests
import json
from qgis.core import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from qgis.PyQt.QtCore import *

The previous code imports the qgis libraries and also requests and json. The requests library will be used to make the API call and json will parse the response from the request to json.

Now it is time to write some code. First, you will need to grab the parameters and set the variables needed to make the API call, and it would not hurt to give the user some information on what is happening. The following code will show you how:

scfcity=City_or_Neighborhood 
searchterm=Filter
progress.setInfo("Wait while I get data from the API")
progress.setText("Calling API")
if searchterm=="None":
pagesURL="http://seeclickfix.com/api/v2/issues?per_page=100&place_url="+scf city+"&page="
url="http://seeclickfix.com/api/v2/issues?per_page=100&place_url="+scfcity
else:
pagesURL="http://seeclickfix.com/api/v2/issuesper_page=100&place_url="+scfc ity+"&search="+searchterm+"&page="
url="http://seeclickfix.com/api/v2/issues?per_page=100&search="+searchterm+"&place_url="+scfcity

The previous code passes the parameters to variables. Then, I used a global variable, progress, which is provided to you by QGIS and calls the setInfo() and setText() methods to tell the user what is happening. progress is available as part of QGIS. The setInfo() method displays text in the text area of the GUI. The setText() method changes the text of the label on the progress bar and adds it to the text area in the GUI as well.

Next, the code checks if the filter parameter is still None, and if it is, it assigns the Uniform Resource Locator (URL) to the API as a string with no filter parameter and uses the city or neighborhood parameter. If there is a filter, a different URL is assigned to make the API call.

Now you are ready for some GIS specific setup. The following code will start you off:

crs=QgsCoordinateReferenceSystem("epsg:4326")
scf=QgsVectorLayer('Point?crs=epsg:4326','SeeClickFix','memory')

fields = QgsFields()
fields.append(QgsField("ID", QVariant.Int))
fields.append(QgsField("Type", QVariant.String))
fields.append(QgsField("Status", QVariant.String))

writer = processing.VectorWriter(Output, None, fields.toList(),
QGis.WKBPoint, crs)

The previous code sets a coordinate reference system in WGS 84. Then, it creates a memory layer, and assigns fields. Lastly, it creates a writer vector and passes the output parameter, encoding (None), the fields, the geometry type, and a coordinate reference system. Now you can make the API call as shown in the code:

r = requests.get(url).text
rJSON=json.loads(r)
pages=rJSON['metadata']['pagination']['pages']
records=rJSON['metadata']['pagination']['entries']
progress.setInfo("Grabbing "+str(records) +" Records")
count=1

for x in range(1,pages+1):
progress.setText("Reading page "+str(x))
pageData=requests.get(pagesURL+str(x)).text
pageDataJSON=json.loads(pageData)

The previous code uses requests to make an API call. It assigns a variable for the number of pages and the number of records returned. Using the setInfo() method, the code tells the user how many records are being processed. It then loops through each page and loads the items from the page. It tells the user what page it is currently reading.

Now, the code will parse each record on the page as a feature and send it to the vector writer. You do not need to add the output to the map. Processing will handle this for you if you run it from the GUI. When you run it from Python, you get the file path to the layer and can load it yourself. The following code will show you how:

for issue in pageDataJSON['issues']:
try:
p=QgsFeature()
point=QgsPoint(issue['lng'],issue['lat'])
p.setGeometry(QgsGeometry.fromPoint(point))
p.setAttributes([issue["id"],issue["request_type"]
["title"],issue["status"]])
writer.addFeature(p)
progress.setPercentage((count/float(records))*100)
count+=1
except:
pass
del writer

The previous code creates a feature and passes the geometry from the API to a point. It then passes the attributes and sends the completed feature to the vector writer. The progress bar on the GUI is updated using progress.setPercentage(). The method takes a float. In this example, the percentage is the number of records processed divided by the total number of records. Lastly, you delete the writer.

Your toolbox is complete, save it. Now a user can run it from the GUI or you can call it from Python. The following code will show you how to call it from Python and add the results to the map:

processing.alghelp("script:seeclickfix")

ALGORITHM: SeeClickFix
City_or_Neighborhood <ParameterString>
Filter <ParameterString>
Output <OutputVector>

out=processing.runalg("script:seeclickfix","Albuquerque","Juan Tabo",None)

The previous code calls the alghelp() method to show our new algorithm and the parameters. Next, it runs the algorithm using runalg() and assigns the results to the out variable. Printing the out variable shows a dictionary with a key of Output and a path to a temporary vector as follows:

out

{'Output': u'C:\Users\Paul\AppData\Local\Temp\processingca7241c6176e42458ea32e8c7264de1e\014bc4d4516240028ce9270b49c5fcaf\Output.shp'}

You can assign the vector to a layer and add it to the map, or you can iterate through the features and do something else with it, as follows:

out = iface.addVectorLayer(str(a["Output"]), "SeeClickFix","ogr")
for feature in out.getFeatures():
Do something...

The results of adding the layer to the map will look like the following screenshot. All of the SeeClickFix incidents reported along the street, Juan Tabo:

Results of the toolbox
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset