1630 lines
67 KiB
Python
Raw Normal View History

2016-07-25 11:01:19 -04:00
#
#
# Author: Luca Cinquini
#
2016-07-25 11:01:19 -04:00
#
"""
Abstract
--------
The wps module of the OWSlib package provides client-side functionality for executing invocations to a remote Web Processing Server.
Disclaimer
----------
PLEASE NOTE: the owslib wps module should be considered in beta state: it has been tested versus only a handful of WPS services (deployed by the USGS, BADC and PML).
More extensive testing is needed and feedback is appreciated.
Usage
-----
2016-07-25 11:01:19 -04:00
The module can be used to execute three types of requests versus a remote WPS endpoint:
2016-07-25 11:01:19 -04:00
a) "GetCapabilities"
- use the method wps.getcapabilities(xml=None)
- the optional keyword argument "xml" may be used to avoid a real live request, and instead read the WPS capabilities document from a cached XML file
2016-07-25 11:01:19 -04:00
b) "DescribeProcess"
- use the method wps.describeprocess(identifier, xml=None)
- identifier is the process identifier, retrieved from the list obtained from a previous "GetCapabilities" invocation
- the optional keyword argument "xml" may be used to avoid a real live request, and instead read the WPS process description document from a cached XML file
2016-07-25 11:01:19 -04:00
c) "Execute"
2016-07-25 11:01:19 -04:00
- use the method wps.execute(identifier, inputs, output=None, request=None, response=None),
which submits the job to the remote WPS server and returns a WPSExecution object that can be used to periodically check the job status until completion
(or error)
2016-07-25 11:01:19 -04:00
- the optional keyword argument "request" may be used to avoid re-building the request XML from input arguments, and instead submit a request from a
pre-made XML file
2016-07-25 11:01:19 -04:00
- alternatively, an "Execute" request can be built from input arguments by supplying the "identifier", "inputs" and "output" arguments to the execute() method.
- "identifier" is the mandatory process identifier
- "inputs" is a dictionary of (key,value) pairs where:
- key is a named input parameter
- value is either a string, or any python object that supports a getXml() method
In particular, a few classes are included in the package to support a FeatuteCollection input:
- "WFSFeatureCollection" can be used in conjunction with "WFSQuery" to define a FEATURE_COLLECTION retrieved from a live WFS server.
- "GMLMultiPolygonFeatureCollection" can be used to define one or more polygons of (latitude, longitude) points.
- "output" is an optional output identifier to be included in the ResponseForm section of the request.
2016-07-25 11:01:19 -04:00
- the optional keyword argument "response" mey be used to avoid submitting a real live request, and instead reading the WPS execution response document
from a cached XML file (for debugging or testing purposes)
- the convenience module function monitorExecution() can be used to periodically check the status of a remote running job, and eventually download the output
either to a named file, or to a file specified by the server.
2016-07-25 11:01:19 -04:00
Examples
--------
2016-07-25 11:01:19 -04:00
The files examples/wps-usgs-script.py, examples/wps-pml-script-1.py and examples/wps-pml-script-2.py contain real-world usage examples
that submits a "GetCapabilities", "DescribeProcess" and "Execute" requests to the live USGS and PML servers. To run:
cd examples
python wps-usgs-script.py
python wps-pml-script-1.py
python wps-pml-script-2.py
2016-07-25 11:01:19 -04:00
The file wps-client.py contains a command-line client that can be used to submit a "GetCapabilities", "DescribeProcess" or "Execute"
request to an arbitratry WPS server. For example, you can run it as follows:
cd examples
To prints out usage and example invocations: wps-client -help
2016-07-25 11:01:19 -04:00
To execute a (fake) WPS invocation:
wps-client.py -v -u http://cida.usgs.gov/climate/gdp/process/WebProcessingService -r GetCapabilities -x ../tests/USGSCapabilities.xml
2016-07-25 11:01:19 -04:00
The directory tests/ includes several doctest-style files wps_*.txt that show how to interactively submit a
"GetCapabilities", "DescribeProcess" or "Execute" request, without making a live request but rather parsing the response of cached XML response documents. To run:
cd tests
python -m doctest wps_*.txt
(or python -m doctest -v wps_*.txt for verbose output)
Also, the directory tests/ contains several examples of well-formed "Execute" requests:
- The files wps_USGSExecuteRequest*.xml contain requests that can be submitted to the live USGS WPS service.
- The files PMLExecuteRequest*.xml contain requests that can be submitted to the live PML WPS service.
"""
2016-07-25 11:01:19 -04:00
from __future__ import (absolute_import, division, print_function)
from owslib.etree import etree
2016-07-25 11:01:19 -04:00
from owslib.ows import DEFAULT_OWS_NAMESPACE, ServiceIdentification, ServiceProvider, OperationsMetadata, BoundingBox
from time import sleep
2016-07-25 11:01:19 -04:00
from owslib.util import (testXMLValue, build_get_url, dump, getTypedValue,
getNamespace, element_to_string, nspath, openURL, nspath_eval, log)
from xml.dom.minidom import parseString
from owslib.namespaces import Namespaces
2016-07-25 11:01:19 -04:00
try: # Python 3
from urllib.parse import urlparse
except ImportError: # Python 2
from urlparse import urlparse
# namespace definition
n = Namespaces()
# These static namespaces are DEPRECIATED. Please don't use them.
# No great way of printing a message since there are at the file level
WPS_DEFAULT_NAMESPACE = n.get_namespace("wps")
WFS_NAMESPACE = n.get_namespace("wfs")
OGC_NAMESPACE = n.get_namespace("ogc")
GML_NAMESPACE = n.get_namespace("gml")
DRAW_NAMESPACE = n.get_namespace("draw")
GML_SCHEMA_LOCATION = "http://schemas.opengis.net/gml/3.1.1/base/feature.xsd"
DRAW_SCHEMA_LOCATION = 'http://cida.usgs.gov/climate/derivative/xsd/draw.xsd'
2016-07-25 11:01:19 -04:00
WFS_SCHEMA_LOCATION = 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd'
WPS_DEFAULT_SCHEMA_LOCATION = 'http://schemas.opengis.net/wps/1.0.0/wpsExecute_request.xsd'
WPS_DEFAULT_VERSION = '1.0.0'
2016-07-25 11:01:19 -04:00
def get_namespaces():
2016-07-25 11:01:19 -04:00
ns = n.get_namespaces(["ogc", "wfs", "wps", "gml", "xsi", "xlink"])
ns[None] = n.get_namespace("wps")
ns["ows"] = DEFAULT_OWS_NAMESPACE
return ns
namespaces = get_namespaces()
2016-07-25 11:01:19 -04:00
def is_reference(val):
"""
2016-07-25 11:01:19 -04:00
Checks if the provided value is a reference (URL).
"""
2016-07-25 11:01:19 -04:00
try:
parsed = urlparse(val)
is_ref = parsed.scheme != ''
except:
is_ref = False
return is_ref
def is_literaldata(val):
"""
Checks if the provided value is a string (includes unicode).
"""
is_str = isinstance(val, str)
if not is_str:
# on python 2.x we need to check unicode
try:
is_str = isinstance(val, unicode)
except:
# unicode is not available on python 3.x
is_str = False
return is_str
def is_complexdata(val):
"""
Checks if the provided value is an implementation of IComplexData.
"""
return hasattr(val, 'getXml')
class IComplexDataInput(object):
"""
Abstract interface representing complex input object for a WPS request.
"""
2016-07-25 11:01:19 -04:00
def getXml(self):
"""
2016-07-25 11:01:19 -04:00
Method that returns the object data as an XML snippet,
to be inserted into the WPS request document sent to the server.
2016-07-25 11:01:19 -04:00
"""
raise NotImplementedError
class WebProcessingService(object):
2016-07-25 11:01:19 -04:00
"""
Class that contains client-side functionality for invoking an OGC Web Processing Service (WPS).
2016-07-25 11:01:19 -04:00
Implements IWebProcessingService.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, url, version=WPS_DEFAULT_VERSION, username=None, password=None, verbose=False, skip_caps=False):
"""
Initialization method resets the object status.
2016-07-25 11:01:19 -04:00
By default it will execute a GetCapabilities invocation to the remote service,
which can be skipped by using skip_caps=True.
"""
2016-07-25 11:01:19 -04:00
# fields passed in from object initializer
self.url = url
self.username = username
self.password = password
self.version = version
self.verbose = verbose
2016-07-25 11:01:19 -04:00
# fields populated by method invocations
self._capabilities = None
self.identification = None
self.provider = None
2016-07-25 11:01:19 -04:00
self.operations = []
self.processes = []
if not skip_caps:
self.getcapabilities()
2016-07-25 11:01:19 -04:00
def getcapabilities(self, xml=None):
"""
Method that requests a capabilities document from the remote WPS server and populates this object's metadata.
keyword argument xml: local XML GetCapabilities document, prevents actual HTTP invocation.
"""
2016-07-25 11:01:19 -04:00
# read capabilities document
2016-07-25 11:01:19 -04:00
reader = WPSCapabilitiesReader(
version=self.version, verbose=self.verbose)
if xml:
# read from stored XML file
self._capabilities = reader.readFromString(xml)
else:
2016-07-25 11:01:19 -04:00
self._capabilities = reader.readFromUrl(
self.url, username=self.username, password=self.password)
log.debug(element_to_string(self._capabilities))
# populate the capabilities metadata obects from the XML tree
self._parseCapabilitiesMetadata(self._capabilities)
2016-07-25 11:01:19 -04:00
def describeprocess(self, identifier, xml=None):
"""
Requests a process document from a WPS service and populates the process metadata.
Returns the process object.
"""
2016-07-25 11:01:19 -04:00
# read capabilities document
2016-07-25 11:01:19 -04:00
reader = WPSDescribeProcessReader(
version=self.version, verbose=self.verbose)
if xml:
# read from stored XML file
rootElement = reader.readFromString(xml)
else:
# read from server
rootElement = reader.readFromUrl(self.url, identifier)
2016-07-25 11:01:19 -04:00
log.info(element_to_string(rootElement))
# build metadata objects
return self._parseProcessMetadata(rootElement)
2016-07-25 11:01:19 -04:00
def execute(self, identifier, inputs, output=None, request=None, response=None):
"""
2016-07-25 11:01:19 -04:00
Submits a WPS process execution request.
Returns a WPSExecution object, which can be used to monitor the status of the job, and ultimately retrieve the result.
2016-07-25 11:01:19 -04:00
identifier: the requested process identifier
inputs: list of process inputs as (key, value) tuples (where value is either a string for LiteralData, or an object for ComplexData)
output: optional identifier for process output reference (if not provided, output will be embedded in the response)
request: optional pre-built XML request document, prevents building of request from other arguments
response: optional pre-built XML response document, prevents submission of request to live WPS server
"""
2016-07-25 11:01:19 -04:00
# instantiate a WPSExecution object
2016-07-25 11:01:19 -04:00
log.info('Executing WPS request...')
execution = WPSExecution(version=self.version, url=self.url,
username=self.username, password=self.password, verbose=self.verbose)
2016-07-25 11:01:19 -04:00
# build XML request from parameters
if request is None:
2016-07-25 11:01:19 -04:00
requestElement = execution.buildRequest(identifier, inputs, output)
request = etree.tostring(requestElement)
execution.request = request
log.debug(request)
# submit the request to the live server
2016-07-25 11:01:19 -04:00
if response is None:
response = execution.submitRequest(request)
else:
response = etree.fromstring(response)
2016-07-25 11:01:19 -04:00
log.debug(etree.tostring(response))
# parse response
execution.parseResponse(response)
2016-07-25 11:01:19 -04:00
return execution
2016-07-25 11:01:19 -04:00
def _parseProcessMetadata(self, rootElement):
"""
Method to parse a <ProcessDescriptions> XML element and returned the constructed Process object
"""
2016-07-25 11:01:19 -04:00
processDescriptionElement = rootElement.find('ProcessDescription')
process = Process(processDescriptionElement, verbose=self.verbose)
2016-07-25 11:01:19 -04:00
# override existing processes in object metadata, if existing already
found = False
for n, p in enumerate(self.processes):
2016-07-25 11:01:19 -04:00
if p.identifier == process.identifier:
self.processes[n] = process
found = True
# otherwise add it
if not found:
self.processes.append(process)
2016-07-25 11:01:19 -04:00
return process
2016-07-25 11:01:19 -04:00
def _parseCapabilitiesMetadata(self, root):
''' Sets up capabilities metadata objects '''
2016-07-25 11:01:19 -04:00
# use the WPS namespace defined in the document root
wpsns = getNamespace(root)
2016-07-25 11:01:19 -04:00
# loop over children WITHOUT requiring a specific namespace
for element in root:
2016-07-25 11:01:19 -04:00
# thie element's namespace
ns = getNamespace(element)
2016-07-25 11:01:19 -04:00
# <ows:ServiceIdentification> metadata
if element.tag.endswith('ServiceIdentification'):
2016-07-25 11:01:19 -04:00
self.identification = ServiceIdentification(
element, namespace=ns)
if self.verbose == True:
dump(self.identification)
2016-07-25 11:01:19 -04:00
# <ows:ServiceProvider> metadata
elif element.tag.endswith('ServiceProvider'):
2016-07-25 11:01:19 -04:00
self.provider = ServiceProvider(element, namespace=ns)
if self.verbose == True:
dump(self.provider)
2016-07-25 11:01:19 -04:00
# <ns0:OperationsMetadata xmlns:ns0="http://www.opengeospatial.net/ows">
# <ns0:Operation name="GetCapabilities">
# <ns0:DCP>
# <ns0:HTTP>
# <ns0:Get xlink:href="http://ceda-wps2.badc.rl.ac.uk/wps?" xmlns:xlink="http://www.w3.org/1999/xlink" />
# </ns0:HTTP>
# </ns0:DCP>
# </ns0:Operation>
# ........
# </ns0:OperationsMetadata>
elif element.tag.endswith('OperationsMetadata'):
2016-07-25 11:01:19 -04:00
for child in element.findall(nspath('Operation', ns=ns)):
self.operations.append(
OperationsMetadata(child, namespace=ns))
if self.verbose == True:
dump(self.operations[-1])
2016-07-25 11:01:19 -04:00
# <wps:ProcessOfferings>
# <wps:Process ns0:processVersion="1.0.0">
# <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">gov.usgs.cida.gdp.wps.algorithm.filemanagement.ReceiveFiles</ows:Identifier>
# <ows:Title xmlns:ows="http://www.opengis.net/ows/1.1">gov.usgs.cida.gdp.wps.algorithm.filemanagement.ReceiveFiles</ows:Title>
# </wps:Process>
# ......
# </wps:ProcessOfferings>
elif element.tag.endswith('ProcessOfferings'):
2016-07-25 11:01:19 -04:00
for child in element.findall(nspath('Process', ns=ns)):
p = Process(child, verbose=self.verbose)
self.processes.append(p)
2016-07-25 11:01:19 -04:00
if self.verbose == True:
dump(self.processes[-1])
2016-07-25 11:01:19 -04:00
class WPSReader(object):
2016-07-25 11:01:19 -04:00
"""
Superclass for reading a WPS document into a lxml.etree infoset.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):
self.version = version
self.verbose = verbose
2016-07-25 11:01:19 -04:00
def _readFromUrl(self, url, data, method='Get', username=None, password=None):
"""
Method to get and parse a WPS document, returning an elementtree instance.
url: WPS service base url.
data: GET: dictionary of HTTP (key, value) parameter pairs, POST: XML document to post
username, password: optional user credentials
"""
2016-07-25 11:01:19 -04:00
if method == 'Get':
# full HTTP request url
request_url = build_get_url(url, data)
2016-07-25 11:01:19 -04:00
log.debug(request_url)
# split URL into base url and query string to use utility function
2016-07-25 11:01:19 -04:00
spliturl = request_url.split('?')
u = openURL(spliturl[0], spliturl[
1], method='Get', username=username, password=password)
return etree.fromstring(u.read())
2016-07-25 11:01:19 -04:00
elif method == 'Post':
2016-07-25 11:01:19 -04:00
u = openURL(url, data, method='Post',
username=username, password=password)
return etree.fromstring(u.read())
2016-07-25 11:01:19 -04:00
else:
raise Exception("Unrecognized HTTP method: %s" % method)
2016-07-25 11:01:19 -04:00
def readFromString(self, string):
"""
Method to read a WPS GetCapabilities document from an XML string.
"""
2016-07-25 11:01:19 -04:00
if not isinstance(string, str) and not isinstance(string, bytes):
raise ValueError(
"Input must be of type string, not %s" % type(string))
return etree.fromstring(string)
class WPSCapabilitiesReader(WPSReader):
2016-07-25 11:01:19 -04:00
"""
Utility class that reads and parses a WPS GetCapabilities document into a lxml.etree infoset.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):
# superclass initializer
2016-07-25 11:01:19 -04:00
super(WPSCapabilitiesReader, self).__init__(
version=version, verbose=verbose)
def readFromUrl(self, url, username=None, password=None):
"""
Method to get and parse a WPS capabilities document, returning an elementtree instance.
url: WPS service base url, to which is appended the HTTP parameters: service, version, and request.
username, password: optional user credentials
"""
2016-07-25 11:01:19 -04:00
return self._readFromUrl(url,
{'service': 'WPS', 'request':
'GetCapabilities', 'version': self.version},
username=username, password=password)
2016-07-25 11:01:19 -04:00
class WPSDescribeProcessReader(WPSReader):
2016-07-25 11:01:19 -04:00
"""
Class that reads and parses a WPS DescribeProcess document into a etree infoset
"""
def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):
# superclass initializer
2016-07-25 11:01:19 -04:00
super(WPSDescribeProcessReader, self).__init__(
version=version, verbose=verbose)
def readFromUrl(self, url, identifier, username=None, password=None):
"""
Reads a WPS DescribeProcess document from a remote service and returns the XML etree object
url: WPS service base url, to which is appended the HTTP parameters: 'service', 'version', and 'request', and 'identifier'.
"""
2016-07-25 11:01:19 -04:00
return self._readFromUrl(url,
{'service': 'WPS', 'request': 'DescribeProcess',
'version': self.version, 'identifier': identifier},
username=username, password=password)
2016-07-25 11:01:19 -04:00
class WPSExecuteReader(WPSReader):
2016-07-25 11:01:19 -04:00
"""
Class that reads and parses a WPS Execute response document into a etree infoset
"""
2016-07-25 11:01:19 -04:00
def __init__(self, verbose=False):
# superclass initializer
2016-07-25 11:01:19 -04:00
super(WPSExecuteReader, self).__init__(verbose=verbose)
def readFromUrl(self, url, data={}, method='Get', username=None, password=None):
2016-07-25 11:01:19 -04:00
"""
Reads a WPS status document from a remote service and returns the XML etree object.
url: the URL to submit the GET/POST request to.
"""
return self._readFromUrl(url, data, method, username=username, password=password)
class WPSExecution():
2016-07-25 11:01:19 -04:00
"""
Class that represents a single WPS process executed on a remote WPS service.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, version=WPS_DEFAULT_VERSION, url=None, username=None, password=None, verbose=False):
2016-07-25 11:01:19 -04:00
# initialize fields
self.url = url
self.version = version
self.username = username
self.password = password
self.verbose = verbose
2016-07-25 11:01:19 -04:00
# request document
self.request = None
2016-07-25 11:01:19 -04:00
# last response document
self.response = None
2016-07-25 11:01:19 -04:00
# status fields retrieved from the response documents
self.process = None
self.serviceInstance = None
self.status = None
self.percentCompleted = 0
self.statusMessage = None
self.errors = []
self.statusLocation = None
2016-07-25 11:01:19 -04:00
self.dataInputs = []
self.processOutputs = []
def buildRequest(self, identifier, inputs=[], output=None):
"""
Method to build a WPS process request.
identifier: the requested process identifier
inputs: array of input arguments for the process.
- LiteralData inputs are expressed as simple (key,value) tuples where key is the input identifier, value is the value
2016-07-25 11:01:19 -04:00
- ComplexData inputs are expressed as (key, object) tuples, where key is the input identifier,
and the object must contain a 'getXml()' method that returns an XML infoset to be included in the WPS request
output: optional identifier if process output is to be returned as a hyperlink reference
"""
2016-07-25 11:01:19 -04:00
#<wps:Execute 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"
# service="WPS"
# version="1.0.0"
# xsi:schemaLocation="http://www.opengis.net/wps/1.0.0
# http://schemas.opengis.net/wps/1.0.0/wpsExecute_request.xsd">
root = etree.Element(nspath_eval('wps:Execute', namespaces))
root.set('service', 'WPS')
root.set('version', WPS_DEFAULT_VERSION)
2016-07-25 11:01:19 -04:00
root.set(nspath_eval('xsi:schemaLocation', namespaces), '%s %s' %
(namespaces['wps'], WPS_DEFAULT_SCHEMA_LOCATION))
# <ows:Identifier>gov.usgs.cida.gdp.wps.algorithm.FeatureWeightedGridStatisticsAlgorithm</ows:Identifier>
2016-07-25 11:01:19 -04:00
identifierElement = etree.SubElement(
root, nspath_eval('ows:Identifier', namespaces))
identifierElement.text = identifier
2016-07-25 11:01:19 -04:00
# <wps:DataInputs>
2016-07-25 11:01:19 -04:00
dataInputsElement = etree.SubElement(
root, nspath_eval('wps:DataInputs', namespaces))
for (key, val) in inputs:
2016-07-25 11:01:19 -04:00
inputElement = etree.SubElement(
dataInputsElement, nspath_eval('wps:Input', namespaces))
identifierElement = etree.SubElement(
inputElement, nspath_eval('ows:Identifier', namespaces))
identifierElement.text = key
2016-07-25 11:01:19 -04:00
# Literal data
# <wps:Input>
# <ows:Identifier>DATASET_URI</ows:Identifier>
# <wps:Data>
# <wps:LiteralData>dods://igsarm-cida-thredds1.er.usgs.gov:8080/thredds/dodsC/dcp/conus_grid.w_meta.ncml</wps:LiteralData>
# </wps:Data>
# </wps:Input>
2016-07-25 11:01:19 -04:00
if is_literaldata(val):
log.debug("literaldata %s", key)
dataElement = etree.SubElement(
inputElement, nspath_eval('wps:Data', namespaces))
literalDataElement = etree.SubElement(
dataElement, nspath_eval('wps:LiteralData', namespaces))
literalDataElement.text = val
2016-07-25 11:01:19 -04:00
# Complex data
# <wps:Input>
# <ows:Identifier>FEATURE_COLLECTION</ows:Identifier>
# <wps:Reference xlink:href="http://igsarm-cida-gdp2.er.usgs.gov:8082/geoserver/wfs">
# <wps:Body>
# <wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" service="WFS" version="1.1.0" outputFormat="text/xml; subtype=gml/3.1.1" xsi:schemaLocation="http://www.opengis.net/wfs ../wfs/1.1.0/WFS.xsd">
# <wfs:Query typeName="sample:CONUS_States">
# <wfs:PropertyName>the_geom</wfs:PropertyName>
# <wfs:PropertyName>STATE</wfs:PropertyName>
# <ogc:Filter>
# <ogc:GmlObjectId gml:id="CONUS_States.508"/>
# </ogc:Filter>
# </wfs:Query>
# </wfs:GetFeature>
# </wps:Body>
# </wps:Reference>
# </wps:Input>
2016-07-25 11:01:19 -04:00
elif is_complexdata(val):
log.debug("complexdata %s", key)
inputElement.append(val.getXml())
else:
2016-07-25 11:01:19 -04:00
raise Exception(
'input type of "%s" parameter is unknown' % key)
# <wps:ResponseForm>
# <wps:ResponseDocument storeExecuteResponse="true" status="true">
# <wps:Output asReference="true">
# <ows:Identifier>OUTPUT</ows:Identifier>
# </wps:Output>
# </wps:ResponseDocument>
# </wps:ResponseForm>
if output is not None:
2016-07-25 11:01:19 -04:00
responseFormElement = etree.SubElement(
root, nspath_eval('wps:ResponseForm', namespaces))
responseDocumentElement = etree.SubElement(
responseFormElement, nspath_eval(
'wps:ResponseDocument', namespaces),
attrib={'storeExecuteResponse': 'true', 'status': 'true'})
if isinstance(output, str):
2016-07-25 11:01:19 -04:00
self._add_output(
responseDocumentElement, output, asReference=True)
elif isinstance(output, list):
2016-07-25 11:01:19 -04:00
for (identifier, as_reference) in output:
self._add_output(
responseDocumentElement, identifier, asReference=as_reference)
else:
2016-07-25 11:01:19 -04:00
raise Exception(
'output parameter is neither string nor list. output=%s' % output)
return root
def _add_output(self, element, identifier, asReference=False):
2016-07-25 11:01:19 -04:00
outputElement = etree.SubElement(
element, nspath_eval('wps:Output', namespaces),
attrib={'asReference': str(asReference).lower()})
outputIdentifierElement = etree.SubElement(
outputElement, nspath_eval('ows:Identifier', namespaces)).text = identifier
# wait for 60 seconds by default
def checkStatus(self, url=None, response=None, sleepSecs=60):
"""
Method to check the status of a job execution.
In the process, this method will upadte the object 'response' attribute.
2016-07-25 11:01:19 -04:00
url: optional 'statusLocation' URL retrieved from a previous WPS Execute response document.
If not provided, the current 'statusLocation' URL will be used.
sleepSecs: number of seconds to sleep before returning control to the caller.
"""
2016-07-25 11:01:19 -04:00
reader = WPSExecuteReader(verbose=self.verbose)
if response is None:
# override status location
if url is not None:
self.statusLocation = url
2016-07-25 11:01:19 -04:00
log.info('\nChecking execution status... (location=%s)' %
self.statusLocation)
response = reader.readFromUrl(
self.statusLocation, username=self.username, password=self.password)
else:
response = reader.readFromString(response)
2016-07-25 11:01:19 -04:00
# store latest response
self.response = etree.tostring(response)
2016-07-25 11:01:19 -04:00
log.debug(self.response)
self.parseResponse(response)
2016-07-25 11:01:19 -04:00
# sleep given number of seconds
2016-07-25 11:01:19 -04:00
if self.isComplete() == False:
log.info('Sleeping %d seconds...' % sleepSecs)
sleep(sleepSecs)
def getStatus(self):
return self.status
2016-07-25 11:01:19 -04:00
def isComplete(self):
2016-07-25 11:01:19 -04:00
if (self.status == 'ProcessSucceeded' or self.status == 'ProcessFailed' or self.status == 'Exception'):
return True
2016-07-25 11:01:19 -04:00
elif (self.status == 'ProcessStarted'):
return False
2016-07-25 11:01:19 -04:00
elif (self.status == 'ProcessAccepted' or self.status == 'ProcessPaused'):
return False
else:
2016-07-25 11:01:19 -04:00
raise Exception(
'Unknown process execution status: %s' % self.status)
def isSucceded(self):
2016-07-25 11:01:19 -04:00
if self.status == 'ProcessSucceeded':
return True
else:
return False
def isNotComplete(self):
return not self.isComplete()
2016-07-25 11:01:19 -04:00
def getOutput(self, filepath=None):
"""
2016-07-25 11:01:19 -04:00
Method to write the outputs of a WPS process to a file:
2014-09-03 08:43:11 -04:00
either retrieves the referenced files from the server, or writes out the content of response embedded output.
2016-07-25 11:01:19 -04:00
filepath: optional path to the output file, otherwise a file will be created in the local directory with the name assigned by the server,
or default name 'wps.out' for embedded output.
"""
2016-07-25 11:01:19 -04:00
if self.isSucceded():
content = ''
for output in self.processOutputs:
2016-07-25 11:01:19 -04:00
output_content = output.retrieveData(
self.username, self.password)
# ExecuteResponse contains reference to server-side output
2014-09-03 08:43:11 -04:00
if output_content is not "":
content = content + output_content
if filepath is None:
filepath = output.fileName
2016-07-25 11:01:19 -04:00
# ExecuteResponse contain embedded output
if len(output.data) > 0:
if filepath is None:
filepath = 'wps.out'
for data in output.data:
content = content + data
2016-07-25 11:01:19 -04:00
# write out content
if content is not '':
out = open(filepath, 'wb')
out.write(content)
out.close()
2016-07-25 11:01:19 -04:00
log.info('Output written to file: %s' % filepath)
else:
2016-07-25 11:01:19 -04:00
raise Exception(
"Execution not successfully completed: status=%s" % self.status)
def submitRequest(self, request):
"""
Submits a WPS Execute document to a remote service, returns the XML response document from the server.
This method will save the request document and the first returned response document.
2016-07-25 11:01:19 -04:00
request: the XML request document to be submitted as POST to the server.
2016-07-25 11:01:19 -04:00
"""
self.request = request
reader = WPSExecuteReader(verbose=self.verbose)
2016-07-25 11:01:19 -04:00
response = reader.readFromUrl(
self.url, request, method='Post', username=self.username, password=self.password)
self.response = response
return response
2016-07-25 11:01:19 -04:00
'''
if response is None:
# override status location
if url is not None:
self.statusLocation = url
2016-07-25 11:01:19 -04:00
else:
response = reader.readFromString(response)
2016-07-25 11:01:19 -04:00
'''
2016-07-25 11:01:19 -04:00
def parseResponse(self, response):
"""
Method to parse a WPS response document
"""
2016-07-25 11:01:19 -04:00
rootTag = response.tag.split('}')[1]
# <ns0:ExecuteResponse>
if rootTag == 'ExecuteResponse':
self._parseExecuteResponse(response)
2016-07-25 11:01:19 -04:00
# <ows:ExceptionReport>
elif rootTag == 'ExceptionReport':
self._parseExceptionReport(response)
2016-07-25 11:01:19 -04:00
else:
2016-07-25 11:01:19 -04:00
log.debug('Unknown Response')
# log status, errors
log.info('Execution status=%s' % self.status)
log.info('Percent completed=%s' % self.percentCompleted)
log.info('Status message=%s' % self.statusMessage)
for error in self.errors:
dump(error)
def _parseExceptionReport(self, root):
"""
Method to parse a WPS ExceptionReport document and populate this object's metadata.
"""
# set exception status, unless set already
if self.status is None:
self.status = "Exception"
2016-07-25 11:01:19 -04:00
for exceptionEl in root.findall(nspath('Exception', ns=namespaces['ows'])):
self.errors.append(WPSException(exceptionEl))
2016-07-25 11:01:19 -04:00
def _parseExecuteResponse(self, root):
"""
Method to parse a WPS ExecuteResponse response document and populate this object's metadata.
"""
2016-07-25 11:01:19 -04:00
# retrieve WPS namespace directly from root element
wpsns = getNamespace(root)
2016-07-25 11:01:19 -04:00
self.serviceInstance = root.get('serviceInstance')
if self.statusLocation is None:
self.statusLocation = root.get('statusLocation')
# <ns0:Status creationTime="2011-11-09T14:19:50Z">
# <ns0:ProcessSucceeded>PyWPS Process v.net.path successfully calculated</ns0:ProcessSucceeded>
# </ns0:Status>
# OR
# <ns0:Status creationTime="2011-11-07T08:26:44.359-06:00">
# <ns0:ProcessFailed>
# <ows:ExceptionReport xmlns:ows="http://www.opengis.net/ows/1.1">
# <ows:Exception>
# <ows:ExceptionText>Attribute null not found in feature collection</ows:ExceptionText>
# </ows:Exception>
# </ows:ExceptionReport>
# </ns0:ProcessFailed>
# </ns0:Status>
2016-07-25 11:01:19 -04:00
statusEl = root.find(nspath('Status/*', ns=wpsns))
self.status = statusEl.tag.split('}')[1]
# get progress info
try:
percentCompleted = int(statusEl.get('percentCompleted'))
self.percentCompleted = percentCompleted
except:
pass
# get status message
self.statusMessage = statusEl.text
# exceptions ?
for element in statusEl:
if element.tag.endswith('ExceptionReport'):
self._parseExceptionReport(element)
2016-07-25 11:01:19 -04:00
self.process = Process(
root.find(nspath('Process', ns=wpsns)), verbose=self.verbose)
#<wps:DataInputs xmlns:wps="http://www.opengis.net/wps/1.0.0"
2016-07-25 11:01:19 -04:00
# xmlns:ows="http://www.opengis.net/ows/1.1"
# xmlns:xlink="http://www.w3.org/1999/xlink">
for inputElement in root.findall(nspath('DataInputs/Input', ns=wpsns)):
self.dataInputs.append(Input(inputElement))
if self.verbose == True:
dump(self.dataInputs[-1])
2016-07-25 11:01:19 -04:00
# <ns:ProcessOutputs>
2016-07-25 11:01:19 -04:00
# xmlns:ns="http://www.opengis.net/wps/1.0.0"
for outputElement in root.findall(nspath('ProcessOutputs/Output', ns=wpsns)):
self.processOutputs.append(Output(outputElement))
if self.verbose == True:
dump(self.processOutputs[-1])
2016-07-25 11:01:19 -04:00
class ComplexData(object):
2016-07-25 11:01:19 -04:00
"""
Class that represents a ComplexData element in a WPS document
"""
2016-07-25 11:01:19 -04:00
def __init__(self, mimeType=None, encoding=None, schema=None):
self.mimeType = mimeType
self.encoding = encoding
self.schema = schema
2016-07-25 11:01:19 -04:00
class InputOutput(object):
2016-07-25 11:01:19 -04:00
"""
Superclass of a WPS input or output data object.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, element):
2016-07-25 11:01:19 -04:00
self.abstract = None
# loop over sub-elements without requiring a specific namespace
for subElement in element:
2016-07-25 11:01:19 -04:00
# <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">SUMMARIZE_TIMESTEP</ows:Identifier>
if subElement.tag.endswith('Identifier'):
2016-07-25 11:01:19 -04:00
self.identifier = testXMLValue(subElement)
# <ows:Title xmlns:ows="http://www.opengis.net/ows/1.1">Summarize Timestep</ows:Title>
elif subElement.tag.endswith('Title'):
2016-07-25 11:01:19 -04:00
self.title = testXMLValue(subElement)
# <ows:Abstract xmlns:ows="http://www.opengis.net/ows/1.1">If selected, processing output will include columns with summarized statistics for all feature attribute values for each timestep</ows:Abstract>
elif subElement.tag.endswith('Abstract'):
2016-07-25 11:01:19 -04:00
self.abstract = testXMLValue(subElement)
self.allowedValues = []
self.supportedValues = []
self.defaultValue = None
self.dataType = None
2016-07-25 11:01:19 -04:00
self.anyValue = False
def _parseData(self, element):
"""
Method to parse a "Data" element
"""
2016-07-25 11:01:19 -04:00
# <ns0:Data>
# <ns0:ComplexData mimeType="text/plain">
2016-07-25 11:01:19 -04:00
# 7504912.93758151 -764109.175074507,7750849.82379226 -22141.8611641468,8561828.42371234 -897195.923493867,7724946.16844165 -602984.014261927
# </ns0:ComplexData>
# </ns0:Data>
2016-07-25 11:01:19 -04:00
# nspath('Data', ns=WPS_NAMESPACE)
complex_data_element = element.find(
nspath('ComplexData', ns=getNamespace(element)))
if complex_data_element is not None:
self.dataType = "ComplexData"
2016-07-25 11:01:19 -04:00
def _parseLiteralData(self, element, literalElementName):
"""
Method to parse the LiteralData element.
"""
2016-07-25 11:01:19 -04:00
# <LiteralData>
# <ows:DataType ows:reference="xs:string" xmlns:ows="http://www.opengis.net/ows/1.1" />
# <ows:AllowedValues xmlns:ows="http://www.opengis.net/ows/1.1">
# <ows:Value>COMMA</ows:Value>
# <ows:Value>TAB</ows:Value>
# <ows:Value>SPACE</ows:Value>
# </ows:AllowedValues>
# <DefaultValue>COMMA</DefaultValue>
# </LiteralData>
2016-07-25 11:01:19 -04:00
# <LiteralData>
# <ows:DataType ows:reference="xs:anyURI" xmlns:ows="http://www.opengis.net/ows/1.1" />
# <ows:AnyValue xmlns:ows="http://www.opengis.net/ows/1.1" />
# </LiteralData>
2016-07-25 11:01:19 -04:00
literal_data_element = element.find(literalElementName)
if literal_data_element is not None:
self.dataType = 'LiteralData'
2016-07-25 11:01:19 -04:00
for sub_element in literal_data_element:
subns = getNamespace(sub_element)
if sub_element.tag.endswith('DataType'):
self.dataType = sub_element.get(
nspath("reference", ns=subns)).split(':')[-1]
for sub_element in literal_data_element:
subns = getNamespace(sub_element)
if sub_element.tag.endswith('DefaultValue'):
self.defaultValue = getTypedValue(
self.dataType, sub_element.text)
if sub_element.tag.endswith('AllowedValues'):
for value in sub_element.findall(nspath('Value', ns=subns)):
self.allowedValues.append(
getTypedValue(self.dataType, value.text))
elif sub_element.tag.endswith('AnyValue'):
self.anyValue = True
def _parseComplexData(self, element, complexDataElementName):
"""
Method to parse a ComplexData or ComplexOutput element.
"""
2016-07-25 11:01:19 -04:00
# <ComplexData>
# <Default>
# <Format>
# <MimeType>text/xml</MimeType>
# <Encoding>UTF-8</Encoding>
# <Schema>http://schemas.opengis.net/gml/2.0.0/feature.xsd</Schema>
# </Format>
# </Default>
# <Supported>
# <Format>
# <MimeType>text/xml</MimeType>
# <Encoding>UTF-8</Encoding>
# <Schema>http://schemas.opengis.net/gml/2.0.0/feature.xsd</Schema>
# </Format>
# <Format>
# <MimeType>text/xml</MimeType>
# <Encoding>UTF-8</Encoding>
# <Schema>http://schemas.opengis.net/gml/2.1.1/feature.xsd</Schema>
# </Format>
# </Supported>
# </ComplexData>
# OR
# <ComplexOutput defaultEncoding="UTF-8" defaultFormat="text/XML" defaultSchema="NONE">
# <SupportedComplexData>
# <Format>text/XML</Format>
# <Encoding>UTF-8</Encoding>
# <Schema>NONE</Schema>
# </SupportedComplexData>
# </ComplexOutput>
2016-07-25 11:01:19 -04:00
complex_data_element = element.find(complexDataElementName)
if complex_data_element is not None:
self.dataType = "ComplexData"
2016-07-25 11:01:19 -04:00
for supported_comlexdata_element in\
complex_data_element.findall('SupportedComplexData'):
self.supportedValues.append(
ComplexData(
mimeType=testXMLValue(
supported_comlexdata_element.find('Format')),
encoding=testXMLValue(
supported_comlexdata_element.find('Encoding')),
schema=testXMLValue(
supported_comlexdata_element.find('Schema'))
)
)
2016-07-25 11:01:19 -04:00
for format_element in\
complex_data_element.findall('Supported/Format'):
self.supportedValues.append(
ComplexData(
mimeType=testXMLValue(format_element.find('MimeType')),
encoding=testXMLValue(format_element.find('Encoding')),
schema=testXMLValue(format_element.find('Schema'))
)
)
2016-07-25 11:01:19 -04:00
default_format_element = complex_data_element.find('Default/Format')
if default_format_element is not None:
self.defaultValue = ComplexData(
mimeType=testXMLValue(
default_format_element.find('MimeType')),
encoding=testXMLValue(
default_format_element.find('Encoding')),
schema=testXMLValue(default_format_element.find('Schema'))
)
def _parseBoundingBoxData(self, element, bboxElementName):
"""
Method to parse the BoundingBoxData element.
"""
# <BoundingBoxData>
# <Default>
# <CRS>epsg:4326</CRS>
# </Default>
# <Supported>
# <CRS>epsg:4326</CRS>
# </Supported>
# </BoundingBoxData>
#
# OR
#
# <BoundingBoxOutput>
# <Default>
# <CRS>epsg:4326</CRS>
# </Default>
# <Supported>
# <CRS>epsg:4326</CRS>
# </Supported>
# </BoundingBoxOutput>
bbox_data_element = element.find(bboxElementName)
if bbox_data_element is not None:
self.dataType = 'BoundingBoxData'
for bbox_element in bbox_data_element.findall('Supported/CRS'):
self.supportedValues.append(bbox_element.text)
default_bbox_element = bbox_data_element.find('Default/CRS')
if default_bbox_element is not None:
self.defaultValue = default_bbox_element.text
class Input(InputOutput):
"""
Class that represents a WPS process input.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, inputElement):
2016-07-25 11:01:19 -04:00
# superclass initializer
2016-07-25 11:01:19 -04:00
super(Input, self).__init__(inputElement)
# <Input maxOccurs="1" minOccurs="0">
# OR
# <MinimumOccurs>1</MinimumOccurs>
self.minOccurs = -1
if inputElement.get("minOccurs") is not None:
2016-07-25 11:01:19 -04:00
self.minOccurs = int(inputElement.get("minOccurs"))
if inputElement.find('MinimumOccurs') is not None:
2016-07-25 11:01:19 -04:00
self.minOccurs = int(
testXMLValue(inputElement.find('MinimumOccurs')))
self.maxOccurs = -1
if inputElement.get("maxOccurs") is not None:
2016-07-25 11:01:19 -04:00
self.maxOccurs = int(inputElement.get("maxOccurs"))
if inputElement.find('MaximumOccurs') is not None:
2016-07-25 11:01:19 -04:00
self.maxOccurs = int(
testXMLValue(inputElement.find('MaximumOccurs')))
# <LiteralData>
self._parseLiteralData(inputElement, 'LiteralData')
2016-07-25 11:01:19 -04:00
# <ComplexData>
self._parseComplexData(inputElement, 'ComplexData')
2016-07-25 11:01:19 -04:00
# <BoundingBoxData>
self._parseBoundingBoxData(inputElement, 'BoundingBoxData')
class Output(InputOutput):
2016-07-25 11:01:19 -04:00
"""
Class that represents a WPS process output.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, outputElement):
2016-07-25 11:01:19 -04:00
# superclass initializer
2016-07-25 11:01:19 -04:00
super(Output, self).__init__(outputElement)
self.reference = None
self.mimeType = None
self.data = []
2014-09-03 08:43:11 -04:00
self.fileName = None
self.filePath = None
2016-07-25 11:01:19 -04:00
# extract wps namespace from outputElement itself
wpsns = getNamespace(outputElement)
2016-07-25 11:01:19 -04:00
# <ns:Reference encoding="UTF-8" mimeType="text/csv"
2016-07-25 11:01:19 -04:00
# href="http://cida.usgs.gov/climate/gdp/process/RetrieveResultServlet?id=1318528582026OUTPUT.601bb3d0-547f-4eab-8642-7c7d2834459e"
# />
referenceElement = outputElement.find(nspath('Reference', ns=wpsns))
if referenceElement is not None:
self.reference = referenceElement.get('href')
self.mimeType = referenceElement.get('mimeType')
2016-07-25 11:01:19 -04:00
# <LiteralOutput>
self._parseLiteralData(outputElement, 'LiteralOutput')
2016-07-25 11:01:19 -04:00
# <ComplexData> or <ComplexOutput>
self._parseComplexData(outputElement, 'ComplexOutput')
2016-07-25 11:01:19 -04:00
# <BoundingBoxData>
self._parseBoundingBoxData(outputElement, 'BoundingBoxOutput')
# <Data>
# <ns0:Data>
# <ns0:ComplexData mimeType="text/plain">
2016-07-25 11:01:19 -04:00
# 7504912.93758151 -764109.175074507,7750849.82379226 -22141.8611641468,8561828.42371234 -897195.923493867,7724946.16844165 -602984.014261927
# </ns0:ComplexData>
# </ns0:Data>
# OR:
# <ns0:Data>
# <ns0:ComplexData encoding="UTF-8" mimeType="text/xml" schema="http://schemas.opengis.net/gml/2.1.2/feature.xsd">
# <ns3:FeatureCollection xsi:schemaLocation="http://ogr.maptools.org/ output_0n7ij9D.xsd" xmlns:ns3="http://ogr.maptools.org/">
# <gml:boundedBy xmlns:gml="http://www.opengis.net/gml">
# <gml:Box>
# <gml:coord><gml:X>-960123.1421801626</gml:X><gml:Y>4665723.56559387</gml:Y></gml:coord>
# <gml:coord><gml:X>-101288.6510608822</gml:X><gml:Y>5108200.011823481</gml:Y></gml:coord>
# </gml:Box>
2016-07-25 11:01:19 -04:00
# </gml:boundedBy>
# <gml:featureMember xmlns:gml="http://www.opengis.net/gml">
# <ns3:output fid="F0">
# <ns3:geometryProperty><gml:LineString><gml:coordinates>-960123.142180162365548,4665723.565593870356679,0 -960123.142180162365548,4665723.565593870356679,0 -960123.142180162598379,4665723.565593870356679,0 -960123.142180162598379,4665723.565593870356679,0 -711230.141176006174646,4710278.48552671354264,0 -711230.141176006174646,4710278.48552671354264,0 -623656.677859728806652,4848552.374973464757204,0 -623656.677859728806652,4848552.374973464757204,0 -410100.337491964863148,4923834.82589447684586,0 -410100.337491964863148,4923834.82589447684586,0 -101288.651060882242746,5108200.011823480948806,0 -101288.651060882242746,5108200.011823480948806,0 -101288.651060882257298,5108200.011823480948806,0 -101288.651060882257298,5108200.011823480948806,0</gml:coordinates></gml:LineString></ns3:geometryProperty>
# <ns3:cat>1</ns3:cat>
# <ns3:id>1</ns3:id>
# <ns3:fcat>0</ns3:fcat>
# <ns3:tcat>0</ns3:tcat>
# <ns3:sp>0</ns3:sp>
# <ns3:cost>1002619.181</ns3:cost>
# <ns3:fdist>0</ns3:fdist>
# <ns3:tdist>0</ns3:tdist>
# </ns3:output>
# </gml:featureMember>
# </ns3:FeatureCollection>
# </ns0:ComplexData>
# </ns0:Data>
2016-07-25 11:01:19 -04:00
#
#
# OWS BoundingBox:
#
# <wps:Data>
# <wps:BoundingBoxData crs="EPSG:4326" dimensions="2">
# <ows:LowerCorner>0.0 -90.0</ows:LowerCorner>
# <ows:UpperCorner>180.0 90.0</ows:UpperCorner>
# </wps:BoundingBoxData>
# </wps:Data>
#
dataElement = outputElement.find(nspath('Data', ns=wpsns))
if dataElement is not None:
2016-07-25 11:01:19 -04:00
complexDataElement = dataElement.find(
nspath('ComplexData', ns=wpsns))
if complexDataElement is not None:
self.dataType = "ComplexData"
self.mimeType = complexDataElement.get('mimeType')
if complexDataElement.text is not None and complexDataElement.text.strip() is not '':
self.data.append(complexDataElement.text.strip())
for child in complexDataElement:
self.data.append(etree.tostring(child))
2016-07-25 11:01:19 -04:00
literalDataElement = dataElement.find(
nspath('LiteralData', ns=wpsns))
if literalDataElement is not None:
self.dataType = literalDataElement.get('dataType')
if literalDataElement.text is not None and literalDataElement.text.strip() is not '':
self.data.append(literalDataElement.text.strip())
2016-07-25 11:01:19 -04:00
bboxDataElement = dataElement.find(
nspath('BoundingBoxData', ns=wpsns))
if bboxDataElement is not None:
self.dataType = "BoundingBoxData"
bbox = BoundingBox(bboxDataElement)
if bbox is not None and bbox.minx is not None:
bbox_value = None
if bbox.crs is not None and bbox.crs.axisorder == 'yx':
bbox_value = "{0},{1},{2},{3}".format(bbox.miny, bbox.minx, bbox.maxy, bbox.maxx)
else:
bbox_value = "{0},{1},{2},{3}".format(bbox.minx, bbox.miny, bbox.maxx, bbox.maxy)
log.debug("bbox=%s", bbox_value)
self.data.append(bbox_value)
2014-09-03 08:43:11 -04:00
def retrieveData(self, username=None, password=None):
"""
2016-07-25 11:01:19 -04:00
Method to retrieve data from server-side reference:
2014-09-03 08:43:11 -04:00
returns "" if the reference is not known.
2016-07-25 11:01:19 -04:00
username, password: credentials to access the remote WPS server
2014-09-03 08:43:11 -04:00
"""
2016-07-25 11:01:19 -04:00
2014-09-03 08:43:11 -04:00
url = self.reference
2016-07-25 11:01:19 -04:00
if url is None:
2014-09-03 08:43:11 -04:00
return ""
2016-07-25 11:01:19 -04:00
2014-09-03 08:43:11 -04:00
# a) 'http://cida.usgs.gov/climate/gdp/process/RetrieveResultServlet?id=1318528582026OUTPUT.601bb3d0-547f-4eab-8642-7c7d2834459e'
# b) 'http://rsg.pml.ac.uk/wps/wpsoutputs/outputImage-11294Bd6l2a.tif'
2016-07-25 11:01:19 -04:00
log.info('Output URL=%s' % url)
2014-09-03 08:43:11 -04:00
if '?' in url:
2016-07-25 11:01:19 -04:00
spliturl = url.split('?')
u = openURL(spliturl[0], spliturl[
1], method='Get', username=username, password=password)
2014-09-03 08:43:11 -04:00
# extract output filepath from URL query string
self.fileName = spliturl[1].split('=')[1]
else:
2016-07-25 11:01:19 -04:00
u = openURL(
url, '', method='Get', username=username, password=password)
2014-09-03 08:43:11 -04:00
# extract output filepath from base URL
self.fileName = url.split('/')[-1]
2016-07-25 11:01:19 -04:00
return u.read()
2014-09-03 08:43:11 -04:00
def writeToDisk(self, path=None, username=None, password=None):
"""
2016-07-25 11:01:19 -04:00
Method to write an output of a WPS process to disk:
2014-09-03 08:43:11 -04:00
it either retrieves the referenced file from the server, or write out the content of response embedded output.
2016-07-25 11:01:19 -04:00
2014-09-03 08:43:11 -04:00
filepath: optional path to the output file, otherwise a file will be created in the local directory with the name assigned by the server,
username, password: credentials to access the remote WPS server
2016-07-25 11:01:19 -04:00
"""
# Check if ExecuteResponse contains reference to server-side output
2014-09-03 08:43:11 -04:00
content = self.retrieveData(username, password)
2016-07-25 11:01:19 -04:00
# ExecuteResponse contain embedded output
if content is "" and len(self.data) > 0:
2014-09-03 08:43:11 -04:00
self.fileName = self.identifier
for data in self.data:
content = content + data
2016-07-25 11:01:19 -04:00
2014-09-03 08:43:11 -04:00
# write out content
if content is not "":
if self.fileName == "":
self.fileName = self.identifier
self.filePath = path + self.fileName
out = open(self.filePath, 'wb')
out.write(content)
out.close()
2016-07-25 11:01:19 -04:00
log.info('Output written to file: %s' % self.filePath)
class WPSException:
2016-07-25 11:01:19 -04:00
"""
Class representing an exception raised by a WPS.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, root):
self.code = root.attrib.get("exceptionCode", None)
self.locator = root.attrib.get("locator", None)
2016-07-25 11:01:19 -04:00
textEl = root.find(nspath('ExceptionText', ns=getNamespace(root)))
if textEl is not None:
self.text = textEl.text
else:
self.text = ""
2016-07-25 11:01:19 -04:00
class Process(object):
2016-07-25 11:01:19 -04:00
"""
Class that represents a WPS process.
"""
2016-07-25 11:01:19 -04:00
def __init__(self, elem, verbose=False):
""" Initialization method extracts all available metadata from an XML document (passed in as etree object) """
2016-07-25 11:01:19 -04:00
# <ns0:ProcessDescriptions service="WPS" version="1.0.0"
# xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsDescribeProcess_response.xsd"
# xml:lang="en-US" xmlns:ns0="http://www.opengis.net/wps/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
# OR:
# <ns0:Process ns0:processVersion="1.0.0">
self._root = elem
self.verbose = verbose
2016-07-25 11:01:19 -04:00
wpsns = getNamespace(elem)
2016-07-25 11:01:19 -04:00
# <ProcessDescription statusSupported="true" storeSupported="true" ns0:processVersion="1.0.0">
2016-07-25 11:01:19 -04:00
self.processVersion = elem.get(nspath('processVersion', ns=wpsns))
self.statusSupported = bool(elem.get("statusSupported"))
self.storeSupported = bool(elem.get("storeSupported"))
self.abstract = None
for child in elem:
2016-07-25 11:01:19 -04:00
# this element's namespace
ns = getNamespace(child)
2016-07-25 11:01:19 -04:00
# <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">gov.usgs.cida.gdp.wps.algorithm.FeatureWeightedGridStatisticsAlgorithm</ows:Identifier>
if child.tag.endswith('Identifier'):
2016-07-25 11:01:19 -04:00
self.identifier = testXMLValue(child)
# <ows:Title xmlns:ows="http://www.opengis.net/ows/1.1">Feature Weighted Grid Statistics</ows:Title>
elif child.tag.endswith('Title'):
2016-07-25 11:01:19 -04:00
self.title = testXMLValue(child)
# <ows:Abstract xmlns:ows="http://www.opengis.net/ows/1.1">This algorithm generates area weighted statistics of a gridded dataset for a set of vector polygon features. Using the bounding-box that encloses the feature data and the time range, if provided, a subset of the gridded dataset is requested from the remote gridded data server. Polygon representations are generated for cells in the retrieved grid. The polygon grid-cell representations are then projected to the feature data coordinate reference system. The grid-cells are used to calculate per grid-cell feature coverage fractions. Area-weighted statistics are then calculated for each feature using the grid values and fractions as weights. If the gridded dataset has a time range the last step is repeated for each time step within the time range or all time steps if a time range was not supplied.</ows:Abstract>
elif child.tag.endswith('Abstract'):
2016-07-25 11:01:19 -04:00
self.abstract = testXMLValue(child)
if self.verbose == True:
dump(self)
2016-07-25 11:01:19 -04:00
# <DataInputs>
self.dataInputs = []
2016-07-25 11:01:19 -04:00
for inputElement in elem.findall('DataInputs/Input'):
self.dataInputs.append(Input(inputElement))
if self.verbose == True:
dump(self.dataInputs[-1], prefix='\tInput: ')
2016-07-25 11:01:19 -04:00
# <ProcessOutputs>
self.processOutputs = []
2016-07-25 11:01:19 -04:00
for outputElement in elem.findall('ProcessOutputs/Output'):
self.processOutputs.append(Output(outputElement))
if self.verbose == True:
dump(self.processOutputs[-1], prefix='\tOutput: ')
2016-07-25 11:01:19 -04:00
class ComplexDataInput(IComplexDataInput, ComplexData):
def __init__(self, value, mimeType=None, encoding=None, schema=None):
super(ComplexDataInput, self).__init__(
mimeType=mimeType, encoding=encoding, schema=schema)
self.value = value
def getXml(self):
if is_reference(self.value):
return self.complexDataAsReference()
else:
return self.complexDataRaw()
def complexDataAsReference(self):
"""
<wps:Reference xlink:href="http://somewhere/test.xml"/>
"""
refElement = etree.Element(nspath_eval('wps:Reference', namespaces),
attrib={nspath_eval("xlink:href", namespaces): self.value})
return refElement
def complexDataRaw(self):
'''
<wps:Data>
<wps:ComplexData mimeType="text/xml" encoding="UTF-8"
schema="http://schemas.opengis.net/gml/3.1.1/base/feature.xsd">
</wps:ComplexData>
</wps:Data>
'''
dataElement = etree.Element(nspath_eval('wps:Data', namespaces))
attrib = dict()
if self.encoding:
attrib['encoding'] = self.encoding
if self.schema:
attrib['schema'] = self.schema
if self.mimeType:
attrib['mimeType'] = self.mimeType
complexDataElement = etree.SubElement(
dataElement, nspath_eval('wps:ComplexData', namespaces), attrib=attrib)
complexDataElement.text = self.value
return dataElement
class FeatureCollection(IComplexDataInput):
'''
Base class to represent a Feature Collection used as input to a WPS request.
The method getXml() is invoked by the WPS execute() method to build the WPS request.
All subclasses must implement the getXml() method to provide their specific XML.
2016-07-25 11:01:19 -04:00
Implements IComplexDataInput.
'''
2016-07-25 11:01:19 -04:00
def __init__(self):
pass
2016-07-25 11:01:19 -04:00
def getXml(self):
raise NotImplementedError
2016-07-25 11:01:19 -04:00
class WFSFeatureCollection(FeatureCollection):
2016-07-25 11:01:19 -04:00
'''
FeatureCollection specified by a WFS query.
All subclasses must implement the getQuery() method to provide the specific query portion of the XML.
'''
2016-07-25 11:01:19 -04:00
def __init__(self, wfsUrl, wfsQuery, wfsMethod=None):
'''
wfsUrl: the WFS service URL
example: wfsUrl = "http://igsarm-cida-gdp2.er.usgs.gov:8082/geoserver/wfs"
wfsQuery : a WFS query instance
'''
self.url = wfsUrl
self.query = wfsQuery
2016-07-25 11:01:19 -04:00
self.method = wfsMethod
# <wps:Reference xlink:href="http://igsarm-cida-gdp2.er.usgs.gov:8082/geoserver/wfs">
# <wps:Body>
# <wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" service="WFS" version="1.1.0" outputFormat="text/xml; subtype=gml/3.1.1" xsi:schemaLocation="http://www.opengis.net/wfs ../wfs/1.1.0/WFS.xsd">
# .......
# </wfs:GetFeature>
# </wps:Body>
# </wps:Reference>
def getXml(self):
2016-07-25 11:01:19 -04:00
root = etree.Element(nspath_eval('wps:Reference', namespaces),
attrib={nspath_eval("xlink:href", namespaces): self.url})
if self.method:
root.attrib['method'] = self.method
bodyElement = etree.SubElement(
root, nspath_eval('wps:Body', namespaces))
getFeatureElement = etree.SubElement(
bodyElement, nspath_eval('wfs:GetFeature', namespaces),
attrib={"service": "WFS",
"version": "1.1.0",
"outputFormat": "text/xml; subtype=gml/3.1.1",
nspath_eval("xsi:schemaLocation", namespaces): "%s %s" % (namespaces['wfs'], WFS_SCHEMA_LOCATION)})
# <wfs:Query typeName="sample:CONUS_States">
# <wfs:PropertyName>the_geom</wfs:PropertyName>
# <wfs:PropertyName>STATE</wfs:PropertyName>
# <ogc:Filter>
# <ogc:GmlObjectId gml:id="CONUS_States.508"/>
# </ogc:Filter>
# </wfs:Query>
2016-07-25 11:01:19 -04:00
getFeatureElement.append(self.query.getXml())
return root
2016-07-25 11:01:19 -04:00
class WFSQuery(IComplexDataInput):
'''
Class representing a WFS query, for insertion into a WFSFeatureCollection instance.
2016-07-25 11:01:19 -04:00
Implements IComplexDataInput.
'''
2016-07-25 11:01:19 -04:00
def __init__(self, typeName, propertyNames=[], filters=[]):
self.typeName = typeName
self.propertyNames = propertyNames
self.filters = filters
2016-07-25 11:01:19 -04:00
def getXml(self):
2016-07-25 11:01:19 -04:00
# <wfs:Query typeName="sample:CONUS_States">
# <wfs:PropertyName>the_geom</wfs:PropertyName>
# <wfs:PropertyName>STATE</wfs:PropertyName>
# <ogc:Filter>
# <ogc:GmlObjectId gml:id="CONUS_States.508"/>
# </ogc:Filter>
# </wfs:Query>
2016-07-25 11:01:19 -04:00
queryElement = etree.Element(
nspath_eval('wfs:Query', namespaces), attrib={"typeName": self.typeName})
for propertyName in self.propertyNames:
2016-07-25 11:01:19 -04:00
propertyNameElement = etree.SubElement(
queryElement, nspath_eval('wfs:PropertyName', namespaces))
propertyNameElement.text = propertyName
2016-07-25 11:01:19 -04:00
if len(self.filters) > 0:
filterElement = etree.SubElement(
queryElement, nspath_eval('ogc:Filter', namespaces))
for filter in self.filters:
2016-07-25 11:01:19 -04:00
gmlObjectIdElement = etree.SubElement(
filterElement, nspath_eval('ogc:GmlObjectId', namespaces),
attrib={nspath_eval('gml:id', namespaces): filter})
return queryElement
2016-07-25 11:01:19 -04:00
class GMLMultiPolygonFeatureCollection(FeatureCollection):
2016-07-25 11:01:19 -04:00
'''
Class that represents a FeatureCollection defined as a GML multi-polygon.
'''
2016-07-25 11:01:19 -04:00
def __init__(self, polygons):
'''
Initializer accepts an array of polygons, where each polygon is an array of (lat,lon) tuples.
Example: polygons = [ [(-102.8184, 39.5273), (-102.8184, 37.418), (-101.2363, 37.418), (-101.2363, 39.5273), (-102.8184, 39.5273)],
[(-92.8184, 39.5273), (-92.8184, 37.418), (-91.2363, 37.418), (-91.2363, 39.5273), (-92.8184, 39.5273)] ]
'''
self.polygons = polygons
2016-07-25 11:01:19 -04:00
def getXml(self):
'''
<wps:Data>
<wps:ComplexData mimeType="text/xml" encoding="UTF-8"
schema="http://schemas.opengis.net/gml/3.1.1/base/feature.xsd">
<gml:featureMembers xmlns:ogc="http://www.opengis.net/ogc"
xmlns:draw="gov.usgs.cida.gdp.draw" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://www.opengis.net/gml"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="gov.usgs.cida.gdp.draw http://cida.usgs.gov/climate/derivative/xsd/draw.xsd">
<gml:box gml:id="box.1">
<gml:the_geom>
<gml:MultiPolygon srsDimension="2"
srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:polygonMember>
<gml:Polygon>
<gml:exterior>
<gml:LinearRing>
<gml:posList>-102.8184 39.5273 -102.8184 37.418 -101.2363 37.418 -101.2363 39.5273 -102.8184 39.5273</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:polygonMember>
</gml:MultiPolygon>
</gml:the_geom>
<gml:ID>0</gml:ID>
</gml:box>
</gml:featureMembers>
</wps:ComplexData>
</wps:Data>
'''
dataElement = etree.Element(nspath_eval('wps:Data', namespaces))
2016-07-25 11:01:19 -04:00
complexDataElement = etree.SubElement(
dataElement, nspath_eval('wps:ComplexData', namespaces),
attrib={"mimeType": "text/xml", "encoding": "UTF-8", "schema": GML_SCHEMA_LOCATION})
featureMembersElement = etree.SubElement(
complexDataElement, nspath_eval('gml:featureMembers', namespaces),
attrib={nspath_eval("xsi:schemaLocation", namespaces): "%s %s" % (DRAW_NAMESPACE, DRAW_SCHEMA_LOCATION)})
boxElement = etree.SubElement(featureMembersElement, nspath_eval(
'gml:box', namespaces), attrib={nspath_eval("gml:id", namespaces): "box.1"})
geomElement = etree.SubElement(
boxElement, nspath_eval('gml:the_geom', namespaces))
multiPolygonElement = etree.SubElement(
geomElement, nspath_eval('gml:MultiPolygon', namespaces),
attrib={"srsDimension": "2", "srsName": "http://www.opengis.net/gml/srs/epsg.xml#4326"})
for polygon in self.polygons:
2016-07-25 11:01:19 -04:00
polygonMemberElement = etree.SubElement(
multiPolygonElement, nspath_eval('gml:polygonMember', namespaces))
polygonElement = etree.SubElement(
polygonMemberElement, nspath_eval('gml:Polygon', namespaces))
exteriorElement = etree.SubElement(
polygonElement, nspath_eval('gml:exterior', namespaces))
linearRingElement = etree.SubElement(
exteriorElement, nspath_eval('gml:LinearRing', namespaces))
posListElement = etree.SubElement(
linearRingElement, nspath_eval('gml:posList', namespaces))
posListElement.text = ' '.join(
["%s %s" % (x, y) for x, y in polygon[:]])
idElement = etree.SubElement(
boxElement, nspath_eval('gml:ID', namespaces))
idElement.text = "0"
return dataElement
2016-07-25 11:01:19 -04:00
def monitorExecution(execution, sleepSecs=3, download=False, filepath=None):
'''
2016-10-01 17:13:02 +02:00
Convenience method to monitor the status of a WPS execution till it completes (successfully or not),
and write the output to file after a successful job completion.
2016-07-25 11:01:19 -04:00
execution: WPSExecution instance
sleepSecs: number of seconds to sleep in between check status invocations
download: True to download the output when the process terminates, False otherwise
filepath: optional path to output file (if downloaded=True), otherwise filepath will be inferred from response document
2016-07-25 11:01:19 -04:00
'''
2016-07-25 11:01:19 -04:00
while execution.isComplete() == False:
execution.checkStatus(sleepSecs=sleepSecs)
2016-07-25 11:01:19 -04:00
log.info('Execution status: %s' % execution.status)
if execution.isSucceded():
if download:
execution.getOutput(filepath=filepath)
else:
2016-07-25 11:01:19 -04:00
for output in execution.processOutputs:
if output.reference is not None:
2016-07-25 11:01:19 -04:00
log.info('Output URL=%s' % output.reference)
else:
for ex in execution.errors:
2016-07-25 11:01:19 -04:00
log.error('Error: code=%s, locator=%s, text=%s' %
(ex.code, ex.locator, ex.text))
def printValue(value):
'''
Utility method to format a value for printing.
'''
2016-07-25 11:01:19 -04:00
# ComplexData type
if isinstance(value, ComplexData):
return "mimeType=%s, encoding=%s, schema=%s" % (value.mimeType, value.encoding, value.schema)
# other type
else:
return value
2016-07-25 11:01:19 -04:00
def printInputOutput(value, indent=''):
'''
Utility method to inspect an input/output element.
'''
# InputOutput fields
2016-07-25 11:01:19 -04:00
print('%s identifier=%s, title=%s, abstract=%s, data type=%s' %
(indent, value.identifier, value.title, value.abstract, value.dataType))
for val in value.allowedValues:
2016-07-25 11:01:19 -04:00
print('%s Allowed Value: %s' % (indent, printValue(val)))
if value.anyValue:
print(' Any value allowed')
for val in value.supportedValues:
2016-07-25 11:01:19 -04:00
print('%s Supported Value: %s' % (indent, printValue(val)))
print('%s Default Value: %s ' % (indent, printValue(value.defaultValue)))
# Input fields
if isinstance(value, Input):
2016-07-25 11:01:19 -04:00
print('%s minOccurs=%d, maxOccurs=%d' %
(indent, value.minOccurs, value.maxOccurs))
# Output fields
if isinstance(value, Output):
2016-07-25 11:01:19 -04:00
print('%s reference=%s, mimeType=%s' %
(indent, value.reference, value.mimeType))
for datum in value.data:
2016-07-25 11:01:19 -04:00
print('%s Data Value: %s' % (indent, printValue(datum)))