Introduction
PyWPS is a great project by Jachym Cepicky and Intevation to provide an open (Open Source and Open Standards) implementation of the OGC Web Processing Service spec. In this article, I will give a quick run through of getting up and running enough to have the obligatory “Hello World” service running.
Check out and install PyWps
The source code for PyWPS can be checked out here (I’m using trunk here which is perhaps not a good idea in a production environment):
svn co https://svn.wald.intevation.org/svn/pywps/trunk pywps-trunk
Also we need to install a couple of dependencies:
sudo apt-get install libapache2-mod-python python-htmltmpl python-magic
Update 6 Jan 2009: python-magic package required as of pywps V3 r878
Right now we can go on to build and install pywps:
sudo python setup.py install
Next we copy or symlink the pywps into your cgi-bin directory:
cd /usr/lib/cgi-bin/ sudo ln -s /usr/local/bin/wps.py .
Apache Configuration
Now you need to configure your apache to set up the correct environment for PyWPS:
sudo vim /etc/apache2/sites-enabled/000-default
For clarity I have added comments to the apache virtual host configuration. Note that on your system you will need to adjust some of the paths shown here to suite.
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
#Next two lines added by Tim for PyWPS
SetEnv PYWPS_CFG /etc/pywps.cfg
SetEnv PYWPS_PROCESSES /home/timlinux/dev/python/wps-processes/clip
PythonPath "['/home/timlinux/dev/python/',
'/home/timlinux/dev/python/wps-processes/clip']
+ sys.path"
AllowOverride None
#Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
#changed from above for pywps
Options +ExecCGI -MultiViews +FollowSymLinks
Order allow,deny
Allow from all
</Directory>
#Alias and dir below added for pywps
Alias /wps_outputs/ "/tmp/wps_outputs"
<Directory "/tmp/wps_outputs/">
Options Indexes MultiViews FollowSymLinks
AllowOverride None
</Directory>
The important part here is that you are setting your PYWPS_CFG path and you python path, and relaxing the cgi-bin permissions a little to allow following symlinks.
PyWps Settings Configuration
As you may have noticed above, pywps uses a file in /etc/pywps.cfg to store all of its global settings. Adapting the settings I have used below should get you started quickly:
[wps] encoding=utf-8 title=PyWPS Development Server version=1.0.0 abstract=Dev version of PyWPS. See http://pywps.wald.intevation.org fees=None constraints=none serveraddress=http://localhost/cgi-bin/wps.py keywords=GRASS,GIS,WPS lang=eng,ger [provider] providerName=Linfiniti Consulting CC. individualName=Tim Sutton positionName=GIS Consultant role=Developer deliveryPoint=Lanseria city=Johannesburg postalCode=000 00 country=za electronicMailAddress=[email protected] providerSite=http://linfiniti.com phoneVoice=False phoneFacsimile=False administrativeArea=False [server] maxoperations=3 maxinputparamlength=1024 maxfilesize=3mb tempPath=/tmp # Dont specify this if you have set it up in the apache config #processesPath= outputUrl=http://linfiniti.com/wps/wps_outputs outputPath=/tmp/wps_outputs debug=true [grass] path=/usr/lib/grass/bin/:/usr/lib/grass/scripts/ addonPath= version=6.2.1 gui=text gisbase=/usr/lib/grass/ ldLibraryPath=/usr/lib/grass/lib gisdbase=/home/user/grassdata #home=/var/www/
Note that the grass section is present, although the use of grass within pywps is entirely optional.
Implementing your own processes module
The last thing we need to do before being able to see pywps in action is to write our processess module. The processes module is basically just a directory filled with python classes, each of which can expose a service endpoint.
First lets set up some structure:
mkdir -p /home/timlinux/dev/python/wps-processes cd /home/timlinux/dev/python/wps-processes
This is our top level processes directory. Under that we will now create a module:
mkdir -p clip cd clip
I called mine ‘clip’ because after we are done, I plan to deploy an image and vector clipping service so I am planning for the future.
echo '__all__ = ["helloWorld"]' > __init__.py
Note that if you want to deploy more than one process, add the additional ones (separated by commas) after the “helloWorld” process.
Next create your process class (mine is unsurprisingly called helloWorld.py):
from pywps.Process.Process import WPSProcess
from types import *
class Process(WPSProcess):
"""Main process class"""
def __init__(self):
"""Process initialization"""
# init process
WPSProcess.__init__(self,
identifier = "helloWorld",
title="Hello World",
version = "0.1",
storeSupported = "false",
statusSupported = "false",
abstract="Pass me a message, I'll pass one back to you",
grassLocation = False)
# process inputs
#simple input
self.message = self.addLiteralInput(identifier = "message",
title = "Your message",
type = StringType)
# literal output
self.textOut = self.addLiteralOutput(identifier="text",
title="Just some literal output",
type = StringType)
def execute(self):
"""Execute process."""
myReply = "Well hello there : " + str(self.message.value)
self.textOut.setValue(myReply)
This is probably one of the most simple process implementations you could write. It takes a single parameter and returns a string.
Testing
Make sure to reload your apache instance before testing e.g.:
sudo /etc/init.d/apache reload
Now you can test.I had to chop these into two lines to fit into my blog page, but you should use a single line url for the purpose:
http://localhost/cgi-bin/wps.py?Service=WPS& Version=1.0.0&Request=GetCapabilities
The above performs a basic capabilities test for your server. You should get a response that looks something like:
<?xml version="1.0" encoding="utf-8"?>
<wps:Capabilities service="WPS" version="1.0.0" xml:lang="eng"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:wps="http://www.opengis.net/wps/1.0.0"
xmlns:ows="http://www.opengis.net/ows/1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wps/1.0.0
http://schemas.opengis.net/wps/1.0.0/wpsGetCapabilities_response.xsd"
updateSequence="1">
<ows:ServiceIdentification>
<ows:Title>PyWPS Development Server</ows:Title>
<ows:Abstract>Development version of PyWPS. See http://pywps.wald.intevation.org</ows:Abstract>
<ows:Keywords>
<ows:Keyword>GRASS</ows:Keyword>
<ows:Keyword>GIS</ows:Keyword>
<ows:Keyword>WPS</ows:Keyword>
</ows:Keywords>
<ows:ServiceType>WPS</ows:ServiceType>
<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
<ows:Fees>None</ows:Fees>
<ows:AccessConstraints>none</ows:AccessConstraints>
</ows:ServiceIdentification>
<ows:ServiceProvider>
<ows:ProviderName>Linfiniti Consulting CC, South Africa</ows:ProviderName>
<ows:ProviderSite xlink:href="http://linfiniti.com"/>
<ows:ServiceContact>
<ows:IndividualName>Tim Sutton</ows:IndividualName>
<ows:PositionName>GIS Consultant</ows:PositionName>
<ows:ContactInfo>
<ows:Address>
<ows:DeliveryPoint>Lanseria</ows:DeliveryPoint>
<ows:City>Johannesburg</ows:City>
<ows:PostalCode>000 00</ows:PostalCode>
<ows:Country>za</ows:Country>
<ows:ElectronicMailAddress>[email protected]</ows:ElectronicMailAddress>
</ows:Address>
</ows:ContactInfo>
</ows:ServiceContact>
</ows:ServiceProvider>
<ows:OperationsMetadata>
<ows:Operation name="GetCapabilities">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="http://localhost/cgi-bin/wps.py?"/>
<ows:Post xlink:href="http://localhost/cgi-bin/wps.py"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="DescribeProcess">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="http://localhost/cgi-bin/wps.py?"/>
<ows:Post xlink:href="http://localhost/cgi-bin/wps.py"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="Execute">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="http://localhost/cgi-bin/wps.py?"/>
<ows:Post xlink:href="http://localhost/cgi-bin/wps.py"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
</ows:OperationsMetadata>
<wps:ProcessOfferings>
<wps:Process wps:processVersion="0.1">
<ows:Identifier>helloWorld</ows:Identifier>
<ows:Title>Hello World</ows:Title>
<ows:Abstract>Pass me a message, I'll pass one back to you</ows:Abstract>
</wps:Process>
</wps:ProcessOfferings>
<wps:Languages>
<wps:Default>
<ows:Language>eng</ows:Language>
</wps:Default>
<wps:Supported>
<ows:Language>eng</ows:Language>
<ows:Language>ger</ows:Language>
</wps:Supported>
</wps:Languages>
<wps:WSDL xlink:href="http://localhost/cgi-bin/wps.py?WSDL"/>
</wps:Capabilities>
http://localhost/cgi-bin/wps.py?Service=WPS&request=DescribeProcess &version=1.0.0&identifier=helloWorld
This will result in something like this which describes our hello world service:
<?xml version="1.0" encoding="utf-8"?>
<wps:ProcessDescriptions xmlns:wps="http://www.opengis.net/wps/1.0.0"
xmlns:ows="http://www.opengis.net/ows/1.1"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wps/1.0.0
http://schemas.opengis.net/wps/1.0.0/wpsDescribeProcess_response.xsd"
service="WPS" version="1.0.0" xml:lang="eng">
<ProcessDescription wps:processVersion="0.1" storeSupported="False" statusSupported="False">
<ows:Identifier>helloWorld</ows:Identifier>
<ows:Title>Hello World</ows:Title>
<ows:Abstract>Pass me a message, I'll pass one back to you</ows:Abstract>
<DataInputs>
<Input minOccurs="1" maxOccurs="1">
<ows:Identifier>message</ows:Identifier>
<ows:Title>Your message</ows:Title>
<LiteralData>
<ows:DataType
ows:reference="http://www.w3.org/TR/xmlschema-2/#string">string</ows:DataType>
<ows:AnyValue />
</LiteralData>
</Input>
</DataInputs>
<ProcessOutputs>
<Output>
<ows:Identifier>text</ows:Identifier>
<ows:Title>Just some literal output</ows:Title>
<LiteralOutput>
<ows:DataType
ows:reference="http://www.w3.org/TR/xmlschema-2/#string">string</ows:DataType>
</LiteralOutput>
</Output>
</ProcessOutputs>
</ProcessDescription>
</wps:ProcessDescriptions>
From the process description we can see that our service takes a single text input and returns a single text output.
Finally we can try to invoke the service itself:
http://localhost/cgi-bin/wps.py?Service=WPS&request=execute& version=1.0.0&identifier=helloWorld&DataInputs=[message=Tim]
Which returns:
<?xml version="1.0" encoding="utf-8"?>
<wps:ExecuteResponse xmlns:wps="http://www.opengis.net/wps/1.0.0"
xmlns:ows="http://www.opengis.net/ows/1.1"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wps/1.0.0
http://schemas.opengis.net/wps/1.0.0/wpsGetCapabilities_response.xsd"
service="WPS" version="1.0.0" xml:lang="eng"
serviceInstance="http://localhost/cgi-bin/wps.py?service=WPS&request=GetCapabilities&version=1.0.0"
statusLocation="http://localhost/wps/wps_outputs/pywps-125414601825.xml">
<wps:Process wps:processVersion="0.1">
<ows:Identifier>helloWorld</ows:Identifier>
<ows:Title>Hello World</ows:Title>
<ows:Abstract>Pass me a message, I'll pass one back to you</ows:Abstract>
</wps:Process>
<wps:Status creationTime="Mon Sep 28 15:53:38 2009">
<wps:ProcessSucceeded>PyWPS Process helloWorld successfully
calculated</wps:ProcessSucceeded>
</wps:Status>
<wps:ProcessOutputs>
<wps:Output>
<ows:Identifier>text</ows:Identifier>
<ows:Title>Just some literal output</ows:Title>
<wps:Data>
<wps:LiteralData dataType="string">
Well hello there : Tim
</wps:LiteralData>
</wps:Data>
</wps:Output>
</wps:ProcessOutputs>
</wps:ExecuteResponse>
You can see in the above output that the string I passed the service was returned to me in the form of a nice greeting.
Conclusion
In the course of an hour or two, you can have a simple PyWPS service running. The creation of new services should be trivial from this point on. I will no doubt post a follow up showing PyWPS doing something more substantial in the future. If you are thirsty for more, do check out the intevation PyWPS documentation pages.
markusN
Just for the record: GRASS 7 now “speaks” WPS directly: http://grass.osgeo.org/wiki/WPS
Tim Sutton
Wow – nice!