Date Mon 28 September 2009

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=tim@linfiniti.com
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>tim@linfiniti.com</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.


Comments

comments powered by Disqus