[owslib] 02/07: Imported Upstream version 0.11.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sat Apr 2 00:40:59 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository owslib.

commit 910ab1ad5915d240b8fc5aca25cfdc1374866cbf
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Apr 2 02:31:03 2016 +0200

    Imported Upstream version 0.11.0
---
 VERSION.txt                                        |    2 +-
 docs/en/index.rst                                  |   74 +-
 owslib/__init__.py                                 |    2 +-
 owslib/csw.py                                      |    3 +
 owslib/feature/__init__.py                         |   11 +
 owslib/feature/schema.py                           |  145 +++
 owslib/feature/wfs100.py                           |   11 +-
 owslib/iso.py                                      |  196 ++--
 owslib/util.py                                     |   67 +-
 owslib/wms.py                                      |    7 -
 owslib/wmts.py                                     |    3 +-
 owslib/wps.py                                      | 1061 ++++++++++++--------
 tests/doctests/csw_geoserver.txt                   |    2 +-
 tests/doctests/csw_linz.txt                        |    4 +-
 tests/doctests/csw_uuid_constrain.txt              |    4 +-
 tests/doctests/gm03_parse.txt                      |    7 +-
 tests/doctests/iso_keywords.txt                    |   26 +
 tests/doctests/tms.txt                             |   28 +-
 tests/doctests/wfs1_generic.txt                    |   33 +
 tests/doctests/wms_GeoServerCapabilities.txt       |    2 +-
 tests/doctests/wmts.txt                            |   11 +-
 tests/doctests/wps_describeprocess_bbox.txt        |   51 +
 .../17bd184a-7e7d-4f81-95a5-041449a7212b_iso.xml   |  235 +++++
 tests/resources/wps_bbox_DescribeProcess.xml       |   39 +
 24 files changed, 1434 insertions(+), 590 deletions(-)

diff --git a/VERSION.txt b/VERSION.txt
index a3f5a8e..d9df1bb 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-0.10.3
+0.11.0
diff --git a/docs/en/index.rst b/docs/en/index.rst
index 5f6ddc2..4543ee0 100644
--- a/docs/en/index.rst
+++ b/docs/en/index.rst
@@ -100,7 +100,7 @@ Anaconda:
    The OWSLib conda packages are **not official** and provided on different conda channels: http://anaconda.org/search?q=type%3Aconda+owslib
 
 .. code-block:: bash
-  
+
   $ conda install -c birdhouse owslib
   # or
   $ conda install -c ioos owslib
@@ -221,7 +221,7 @@ Connect to a WFS and inspect its capabilities.
 
     >>> from owslib.wfs import WebFeatureService
     >>> wfs11 = WebFeatureService(url='http://geoserv.weichand.de:8080/geoserver/wfs', version='1.1.0')
-    >>> wfs11.identification.title    
+    >>> wfs11.identification.title
     'INSPIRE WFS 2.0 DemoServer Verwaltungsgrenzen Bayern
 
     >>> [operation.name for operation in wfs11.operations]
@@ -261,7 +261,7 @@ Save response to a file.
 
     >>> out = open('/tmp/data.gml', 'wb')
     >>> out.write(bytes(response.read(), 'UTF-8'))
-    >>> out.close()    
+    >>> out.close()
 
 Download GML using ``StoredQueries``\ (only available for WFS 2.0
 services)
@@ -307,7 +307,7 @@ Get supported resultType's:
   >>> csw.getdomain('GetRecords.resultType')
   >>> csw.results
   {'values': ['results', 'validate', 'hits'], 'parameter': 'GetRecords.resultType', 'type': 'csw:DomainValuesType'}
-  >>> 
+  >>>
 
 Search for bird data:
 
@@ -316,11 +316,11 @@ Search for bird data:
   >>> from owslib.fes import PropertyIsEqualTo, PropertyIsLike, BBox
   >>> birds_query = PropertyIsEqualTo('csw:AnyText', 'birds')
   >>> csw.getrecords2(constraints=[birds_query], maxrecords=20)
-  >>> csw.results    
+  >>> csw.results
   {'matches': 101, 'nextrecord': 21, 'returned': 20}
   >>> for rec in csw.records:
-  ...     print csw.records[rec].title    
-  ... 
+  ...     print csw.records[rec].title
+  ...
   ALLSPECIES
   NatureServe Canada References
   Bird Studies Canada - BirdMap WMS
@@ -341,7 +341,7 @@ Search for bird data:
   Parks Canada Geomatics Metadata Repository
   Breeding Bird Survey
   SCALE
-  >>> 
+  >>>
 
 Search for bird data in Canada:
 
@@ -351,7 +351,7 @@ Search for bird data in Canada:
   >>> csw.getrecords2(constraints=[birds_query, bbox_query])
   >>> csw.results
   {'matches': 3, 'nextrecord': 0, 'returned': 3}
-  >>> 
+  >>>
 
 Search for keywords like 'birds' or 'fowl'
 
@@ -362,7 +362,7 @@ Search for keywords like 'birds' or 'fowl'
   >>> csw.getrecords2(constraints=[birds_query_like, fowl_query_like])
   >>> csw.results
   {'matches': 107, 'nextrecord': 11, 'returned': 10}
-  >>> 
+  >>>
 
 Search for a specific record:
 
@@ -435,20 +435,20 @@ Inspect a remote WPS and retrieve the supported processes:
 	'Geo Data Portal WPS Processing'
 	>>> for operation in wps.operations:
 	...     operation.name
-	... 
+	...
 	'GetCapabilities'
 	'DescribeProcess'
 	'Execute'
 	>>> for process in wps.processes:
 	...     process.identifier, process.title
-	...     
+	...
 	('gov.usgs.cida.gdp.wps.algorithm.FeatureCoverageIntersectionAlgorithm', 'Feature Coverage WCS Intersection')
 	('gov.usgs.cida.gdp.wps.algorithm.FeatureCoverageOPeNDAPIntersectionAlgorithm', 'Feature Coverage OPeNDAP Intersection')
 	('gov.usgs.cida.gdp.wps.algorithm.FeatureCategoricalGridCoverageAlgorithm', 'Feature Categorical Grid Coverage')
 	('gov.usgs.cida.gdp.wps.algorithm.FeatureWeightedGridStatisticsAlgorithm', 'Feature Weighted Grid Statistics')
 	('gov.usgs.cida.gdp.wps.algorithm.FeatureGridStatisticsAlgorithm', 'Feature Grid Statistics')
 	('gov.usgs.cida.gdp.wps.algorithm.PRMSParameterGeneratorAlgorithm', 'PRMS Parameter Generator')
-	>>> 
+	>>>
 
 
 Determine how a specific process needs to be invoked - i.e. what are its input parameters, and output result:
@@ -465,7 +465,7 @@ Determine how a specific process needs to be invoked - i.e. what are its input p
 	'This algorithm generates area weighted statistics of a gridded dataset for a set of vector polygon features. Using the bounding-box that encloses ...
 	>>> for input in process.dataInputs:
 	...     printInputOutput(input)
-	... 
+	...
 	 identifier=FEATURE_COLLECTION, title=Feature Collection, abstract=A feature collection encoded as a WFS request or one of the supported GML profiles.,...
 	 Supported Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/2.0.0/feature.xsd
 	 Supported Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/2.1.1/feature.xsd
@@ -476,37 +476,37 @@ Determine how a specific process needs to be invoked - i.e. what are its input p
 	 Supported Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/3.1.0/base/feature.xsd
 	 Supported Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/3.1.1/base/feature.xsd
 	 Supported Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/3.2.1/base/feature.xsd
-	 Default Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/2.0.0/feature.xsd 
+	 Default Value: mimeType=text/xml, encoding=UTF-8, schema=http://schemas.opengis.net/gml/2.0.0/feature.xsd
 	 minOccurs=1, maxOccurs=1
 	 identifier=DATASET_URI, title=Dataset URI, abstract=The base data web service URI for the dataset of interest., data type=anyURI
 	 Allowed Value: AnyValue
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=1, maxOccurs=1
 	 identifier=DATASET_ID, title=Dataset Identifier, abstract=The unique identifier for the data type or variable of interest., data type=string
 	 Allowed Value: AnyValue
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=1, maxOccurs=2147483647
 	 identifier=REQUIRE_FULL_COVERAGE, title=Require Full Coverage, abstract=If turned on, the service will require that the dataset of interest ....
 	 Allowed Value: True
-	 Default Value: True 
+	 Default Value: True
 	 minOccurs=1, maxOccurs=1
 	 identifier=TIME_START, title=Time Start, abstract=The date to begin analysis., data type=dateTime
 	 Allowed Value: AnyValue
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=0, maxOccurs=1
 	 identifier=TIME_END, title=Time End, abstract=The date to end analysis., data type=dateTime
 	 Allowed Value: AnyValue
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=0, maxOccurs=1
 	 identifier=FEATURE_ATTRIBUTE_NAME, title=Feature Attribute Name, abstract=The attribute that will be used to label column headers in processing output., ...
 	 Allowed Value: AnyValue
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=1, maxOccurs=1
 	 identifier=DELIMITER, title=Delimiter, abstract=The delimiter that will be used to separate columns in the processing output., data type=string
 	 Allowed Value: COMMA
 	 Allowed Value: TAB
 	 Allowed Value: SPACE
-	 Default Value: COMMA 
+	 Default Value: COMMA
 	 minOccurs=1, maxOccurs=1
 	 identifier=STATISTICS, title=Statistics, abstract=Statistics that will be returned for each feature in the processing output., data type=string
 	 Allowed Value: MEAN
@@ -516,29 +516,29 @@ Determine how a specific process needs to be invoked - i.e. what are its input p
 	 Allowed Value: STD_DEV
 	 Allowed Value: SUM
 	 Allowed Value: COUNT
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=1, maxOccurs=7
 	 identifier=GROUP_BY, title=Group By, abstract=If multiple features and statistics are selected, this will change whether the processing output ...
 	 Allowed Value: STATISTIC
 	 Allowed Value: FEATURE_ATTRIBUTE
-	 Default Value: None 
+	 Default Value: None
 	 minOccurs=1, maxOccurs=1
 	 identifier=SUMMARIZE_TIMESTEP, title=Summarize Timestep, abstract=If selected, processing output will include columns with summarized statistics ...
 	 Allowed Value: True
-	 Default Value: True 
+	 Default Value: True
 	 minOccurs=0, maxOccurs=1
 	 identifier=SUMMARIZE_FEATURE_ATTRIBUTE, title=Summarize Feature Attribute, abstract=If selected, processing output will include a final row of ...
 	 Allowed Value: True
-	 Default Value: True 
+	 Default Value: True
 	 minOccurs=0, maxOccurs=1
 	>>> for output in process.processOutputs:
 	...     printInputOutput(output)
-	... 
+	...
 	 identifier=OUTPUT, title=Output File, abstract=A delimited text file containing requested process output., data type=ComplexData
 	 Supported Value: mimeType=text/csv, encoding=UTF-8, schema=None
-	 Default Value: mimeType=text/csv, encoding=UTF-8, schema=None 
+	 Default Value: mimeType=text/csv, encoding=UTF-8, schema=None
 	 reference=None, mimeType=None
-	>>> 
+	>>>
 
 Submit a processing request (extraction of a climate index variable over a specific GML polygon, for a given period of time), monitor the execution until complete:
 
@@ -567,12 +567,12 @@ Submit a processing request (extraction of a climate index variable over a speci
 	Execution status=ProcessStarted
 	>>> from owslib.wps import monitorExecution
 	>>> monitorExecution(execution)
-	
+
 	Checking execution status... (location=http://cida.usgs.gov/climate/gdp/process/RetrieveResultServlet?id=6809217153012787208)
 	Execution status=ProcessSucceeded
 	Execution status: ProcessSucceeded
 	Output URL=http://cida.usgs.gov/climate/gdp/process/RetrieveResultServlet?id=6809217153012787208OUTPUT.3cbcd666-a912-456f-84a3-6ede450aca95
-	>>> 
+	>>>
 
 Alternatively, define the feature through an embedded query to a WFS server:
 
@@ -583,7 +583,7 @@ Alternatively, define the feature through an embedded query to a WFS server:
 	>>> query = WFSQuery("sample:CONUS_States", propertyNames=['the_geom',"STATE"], filters=["CONUS_States.508","CONUS_States.469"])
 	>>> featureCollection = WFSFeatureCollection(wfsUrl, query)
 	>>> # same process submission as above
-	... 
+	...
 
 You can also submit a pre-made request encoded as WPS XML:
 
@@ -594,7 +594,7 @@ You can also submit a pre-made request encoded as WPS XML:
 	Executing WPS request...
 	Execution status=ProcessStarted
 	>>> monitorExecution(execution)
-	
+
 	Checking execution status... (location=http://cida.usgs.gov/climate/gdp/process/RetrieveResultServlet?id=5103866488472745994)
 	Execution status=ProcessSucceeded
 	Execution status: ProcessSucceeded
@@ -622,10 +622,10 @@ ISO
 .. code-block:: python
 
   >>> from owslib.iso import *
-  >>> m=MD_Metadata(etree.parse('tests/resources/9250AA67-F3AC-6C12-0CB9-0662231AA181_iso.xml')
+  >>> m=MD_Metadata(etree.parse('tests/resources/9250AA67-F3AC-6C12-0CB9-0662231AA181_iso.xml'))
   >>> m.identification.topiccategory
   'farming'
-  >>> 
+  >>>
 
 ISO Codelists:
 
@@ -690,7 +690,7 @@ Or ...
     # install requirements
     $ pip install -r requirements.txt
     $ pip install -r requirements-dev.txt # needed for tests only
- 
+
     # run tests
     python -m pytest
 
@@ -787,7 +787,7 @@ Credits
 .. _`OGC WMTS`: http://www.opengeospatial.org/standards/wmts
 .. _`OGC Filter`: http://www.opengeospatial.org/standards/filter
 .. _`OGC OWS Common`: http://www.opengeospatial.org/standards/common
-.. _`NASA DIF`: http://gcmd.nasa.gov/User/difguide/ 
+.. _`NASA DIF`: http://gcmd.nasa.gov/User/difguide/
 .. _`FGDC CSDGM`: http://www.fgdc.gov/metadata/csdgm
 .. _`ISO 19115`: http://www.iso.org/iso/catalogue_detail.htm?csnumber=26020
 .. _`ISO 19139`: http://www.iso.org/iso/catalogue_detail.htm?csnumber=32557
diff --git a/owslib/__init__.py b/owslib/__init__.py
index 9bfec85..145435f 100644
--- a/owslib/__init__.py
+++ b/owslib/__init__.py
@@ -1,3 +1,3 @@
 from __future__ import (absolute_import, division, print_function)
 
-__version__ = '0.10.3'
+__version__ = '0.11.0'
diff --git a/owslib/csw.py b/owslib/csw.py
index 68eac17..c6eb08b 100644
--- a/owslib/csw.py
+++ b/owslib/csw.py
@@ -324,6 +324,9 @@ class CatalogueServiceWeb(object):
             val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces))
             if val is not None:
                 esn = util.testXMLValue(val)
+            val = self.request.attrib.get('outputSchema')
+            if val is not None:
+                outputschema = util.testXMLValue(val, True)
         else:
             # construct request
             node0 = self._setrootelement('csw:GetRecords')
diff --git a/owslib/feature/__init__.py b/owslib/feature/__init__.py
index 2082e3f..6d93c00 100644
--- a/owslib/feature/__init__.py
+++ b/owslib/feature/__init__.py
@@ -15,6 +15,7 @@ except ImportError:
     from urllib.parse import urlencode
 import logging
 from owslib.util import log
+from owslib.feature.schema import get_schema
 
 class WebFeatureService_(object):
     """Base class for WebFeatureService implementations"""
@@ -146,3 +147,13 @@ class WebFeatureService_(object):
         data = urlencode(request)
 
         return base_url+data
+
+
+    def get_schema(self, typename):
+        """
+        Get layer schema compatible with :class:`fiona` schema object
+        """
+
+        return get_schema(self.url, typename, self.version)
+    
+
diff --git a/owslib/feature/schema.py b/owslib/feature/schema.py
new file mode 100644
index 0000000..8a8561e
--- /dev/null
+++ b/owslib/feature/schema.py
@@ -0,0 +1,145 @@
+# =============================================================================
+# OWSLib. Copyright (C) 2015 Jachym Cepicky
+#
+# Contact email: jachym.cepicky at gmail.com
+#
+# =============================================================================
+"""
+Set of functions, which are suitable for DescribeFeatureType parsing and
+generating layer schema description compatible with `fiona`
+"""
+
+import cgi, sys
+from owslib.util import openURL
+try:
+    from urllib import urlencode
+except ImportError:
+    from urllib.parse import urlencode
+from owslib.etree import etree
+from owslib.namespaces import Namespaces
+from owslib.util import which_etree, findall
+
+MYNS = Namespaces()
+XS_NAMESPACE = MYNS.get_namespace('xs')
+GML_NAMESPACES = (MYNS.get_namespace('gml'),
+                  MYNS.get_namespace('gml311'),
+                  MYNS.get_namespace('gml32'))
+
+
+def get_schema(url, typename, version='1.0.0', timeout=30):
+    """Parses DescribeFeatureType response and creates schema compatible
+    with :class:`fiona`
+
+    :param str url: url of the service
+    :param str version: version of the service
+    :param str typename: name of the layer
+    :param int timeout: request timeout
+    """
+
+    url = _get_describefeaturetype_url(url, version, typename)
+    res = openURL(url, timeout=timeout)
+    root = etree.fromstring(res.read())
+    type_element = findall(root, '{%s}element' % XS_NAMESPACE,
+                           attribute_name='name', attribute_value=typename)[0]
+    complex_type = type_element.attrib['type'].split(":")[1]
+    elements = _get_elements(complex_type, root)
+    nsmap = None
+    if hasattr(root, 'nsmap'):
+        nsmap = root.nsmap
+    return _construct_schema(elements, nsmap)
+
+
+def _get_elements(complex_type, root):
+    """Get attribute elements
+    """
+
+    found_elements = []
+    element = findall(root, '{%s}complexType' % XS_NAMESPACE,
+                       attribute_name='name', attribute_value=complex_type)[0]
+    found_elements = findall(element, '{%s}element' % XS_NAMESPACE)
+
+    return found_elements
+
+def _construct_schema(elements, nsmap):
+    """Consruct fiona schema based on given elements
+
+    :param list Element: list of elements
+    :param dict nsmap: namespace map
+
+    :return dict: schema
+    """
+
+    schema = {
+        'properties': {},
+        'geometry': None
+    }
+
+    schema_key = None
+    gml_key = None
+
+    # if nsmap is defined, use it
+    if nsmap:
+        for key in nsmap:
+            if nsmap[key] == XS_NAMESPACE:
+                schema_key = key
+            if nsmap[key] in GML_NAMESPACES:
+                gml_key = key
+    # if no nsmap is defined, we have to guess
+    else:
+        gml_key = 'gml'
+        schema_key = 'xsd'
+
+    for element in elements:
+        data_type = element.attrib['type'].replace(gml_key + ':', '')
+        name = element.attrib['name']
+
+        if data_type == 'PointPropertyType':
+            schema['geometry'] = 'Point'
+        elif data_type == 'PolygonPropertyType':
+            schema['geometry'] = 'Polygon'
+        elif data_type == 'LineStringPropertyType':
+            schema['geometry'] = 'LineString'
+        elif data_type == 'MultiPointPropertyType':
+            schema['geometry'] = 'MultiPoint'
+        elif data_type == 'MultiLineStringPropertyType':
+            schema['geometry'] = 'MultiLineString'
+        elif data_type == 'MultiPolygonPropertyType':
+            schema['geometry'] = 'MultiPolygon'
+        elif data_type == 'MultiGeometryPropertyType':
+            schema['geometry'] = 'MultiGeometry'
+        elif data_type == 'GeometryPropertyType':
+            schema['geometry'] = 'GeometryCollection'
+        elif data_type == 'SurfacePropertyType':
+            schema['geometry'] = '3D Polygon'
+        else:
+            schema['properties'][name] = data_type.replace(schema_key+':', '')
+
+    if schema['properties'] or schema['geometry']:
+        return schema
+    else:
+        return None
+
+def _get_describefeaturetype_url(url, version, typename):
+    """Get url for describefeaturetype request
+
+    :return str: url
+    """
+
+    query_string = []
+    if url.find('?') != -1:
+        query_string = cgi.parse_qsl(url.split('?')[1])
+
+    params = [x[0] for x in query_string]
+
+    if 'service' not in params:
+        query_string.append(('service', 'WFS'))
+    if 'request' not in params:
+        query_string.append(('request', 'DescribeFeatureType'))
+    if 'version' not in params:
+        query_string.append(('version', version))
+
+    query_string.append(('typeName', typename))
+
+    urlqs = urlencode(tuple(query_string))
+    return url.split('?')[0] + '?' + urlqs
+
diff --git a/owslib/feature/wfs100.py b/owslib/feature/wfs100.py
index d1eeba8..13ebedb 100644
--- a/owslib/feature/wfs100.py
+++ b/owslib/feature/wfs100.py
@@ -22,6 +22,7 @@ from owslib.iso import MD_Metadata
 from owslib.crs import Crs
 from owslib.namespaces import Namespaces
 from owslib.util import log
+from owslib.feature.schema import get_schema
 
 import pyproj
 
@@ -261,6 +262,15 @@ class WebFeatureService_1_0_0(object):
                 return item
         raise KeyError("No operation named %s" % name)
 
+    def get_schema(self, typename):
+        """
+        Get layer schema compatible with :class:`fiona` schema object
+        """
+
+        return get_schema(self.url, typename, self.version)
+    
+
+
 class ServiceIdentification(object):
     ''' Implements IServiceIdentificationMetadata '''
     
@@ -428,4 +438,3 @@ class WFSCapabilitiesReader(object):
         if not isinstance(st, str) and not isinstance(st, bytes):
             raise ValueError("String must be of type string or bytes, not %s" % type(st))
         return etree.fromstring(st)
-    
diff --git a/owslib/iso.py b/owslib/iso.py
index cad89e3..c23b2dc 100644
--- a/owslib/iso.py
+++ b/owslib/iso.py
@@ -1,5 +1,5 @@
-# -*- coding: ISO-8859-15 -*- 
-# ============================================================================= 
+# -*- coding: ISO-8859-15 -*-
+# =============================================================================
 # Copyright (c) 2009 Tom Kralidis
 #
 # Authors : Tom Kralidis <tomkralidis at gmail.com>
@@ -11,6 +11,7 @@
 """ ISO metadata parser """
 
 from __future__ import (absolute_import, division, print_function)
+import warnings
 
 from owslib.etree import etree
 from owslib import util
@@ -63,13 +64,13 @@ class MD_Metadata(object):
 
             val = md.find(util.nspath_eval('gmd:language/gco:CharacterString', namespaces))
             self.language = util.testXMLValue(val)
-            
+
             val = md.find(util.nspath_eval('gmd:dataSetURI/gco:CharacterString', namespaces))
             self.dataseturi = util.testXMLValue(val)
 
             val = md.find(util.nspath_eval('gmd:language/gmd:LanguageCode', namespaces))
-            self.languagecode = util.testXMLValue(val)
-            
+            self.languagecode = util.testXMLAttribute(val, 'codeListValue')
+
             val = md.find(util.nspath_eval('gmd:dateStamp/gco:Date', namespaces))
             self.datestamp = util.testXMLValue(val)
 
@@ -78,17 +79,17 @@ class MD_Metadata(object):
                 self.datestamp = util.testXMLValue(val)
 
             self.charset = _testCodeListValue(md.find(util.nspath_eval('gmd:characterSet/gmd:MD_CharacterSetCode', namespaces)))
-      
+
             self.hierarchy = _testCodeListValue(md.find(util.nspath_eval('gmd:hierarchyLevel/gmd:MD_ScopeCode', namespaces)))
 
             self.contact = []
             for i in md.findall(util.nspath_eval('gmd:contact/gmd:CI_ResponsibleParty', namespaces)):
                 o = CI_ResponsibleParty(i)
                 self.contact.append(o)
-            
+
             val = md.find(util.nspath_eval('gmd:dateStamp/gco:DateTime', namespaces))
             self.datetimestamp = util.testXMLValue(val)
-            
+
             val = md.find(util.nspath_eval('gmd:metadataStandardName/gco:CharacterString', namespaces))
             self.stdname = util.testXMLValue(val)
 
@@ -102,12 +103,12 @@ class MD_Metadata(object):
                 self.referencesystem = None
 
             # TODO: merge .identificationinfo into .identification
-            #warnings.warn(
-            #    'the .identification and .serviceidentification properties will merge into '
-            #    '.identification being a list of properties.  This is currently implemented '
-            #    'in .identificationinfo.  '
-            #    'Please see https://github.com/geopython/OWSLib/issues/38 for more information',
-            #    FutureWarning)
+            warnings.warn(
+                'the .identification and .serviceidentification properties will merge into '
+                '.identification being a list of properties.  This is currently implemented '
+                'in .identificationinfo.  '
+                'Please see https://github.com/geopython/OWSLib/issues/38 for more information',
+                FutureWarning)
 
             val = md.find(util.nspath_eval('gmd:identificationInfo/gmd:MD_DataIdentification', namespaces))
             val2 = md.find(util.nspath_eval('gmd:identificationInfo/srv:SV_ServiceIdentification', namespaces))
@@ -124,14 +125,14 @@ class MD_Metadata(object):
 
             self.identificationinfo = []
             for idinfo in md.findall(util.nspath_eval('gmd:identificationInfo', namespaces)):
-                if len(idinfo) > 0:    
+                if len(idinfo) > 0:
                     val = list(idinfo)[0]
                     tagval = util.xmltag_split(val.tag)
-                    if tagval == 'MD_DataIdentification': 
+                    if tagval == 'MD_DataIdentification':
                         self.identificationinfo.append(MD_DataIdentification(val, 'dataset'))
-                    elif tagval == 'MD_ServiceIdentification': 
+                    elif tagval == 'MD_ServiceIdentification':
                         self.identificationinfo.append(MD_DataIdentification(val, 'service'))
-                    elif tagval == 'SV_ServiceIdentification': 
+                    elif tagval == 'SV_ServiceIdentification':
                         self.identificationinfo.append(SV_ServiceIdentification(val))
 
             val = md.find(util.nspath_eval('gmd:distributionInfo/gmd:MD_Distribution', namespaces))
@@ -140,7 +141,7 @@ class MD_Metadata(object):
                 self.distribution = MD_Distribution(val)
             else:
                 self.distribution = None
-            
+
             val = md.find(util.nspath_eval('gmd:dataQualityInfo/gmd:DQ_DataQuality', namespaces))
             if val is not None:
                 self.dataquality = DQ_DataQuality(val)
@@ -225,9 +226,45 @@ class CI_ResponsibleParty(object):
               self.onlineresource = CI_OnlineResource(val)
             else:
               self.onlineresource = None
-          
+
             self.role = _testCodeListValue(md.find(util.nspath_eval('gmd:role/gmd:CI_RoleCode', namespaces)))
 
+
+class MD_Keywords(object):
+    """
+    Class for the metadata MD_Keywords element
+    """
+    def __init__(self, md=None):
+        if md is None:
+            self.keywords = []
+            self.type = None
+            self.thesaurus = None
+            self.kwdtype_codeList = 'http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/codelist/gmxCodelists.xml#MD_KeywordTypeCode'
+        else:
+            self.keywords = []
+            val = md.findall(util.nspath_eval('gmd:keyword/gco:CharacterString', namespaces))
+            for word in val:
+                self.keywords.append(util.testXMLValue(word))
+
+            self.type = None
+            val = md.find(util.nspath_eval('gmd:type/gmd:MD_KeywordTypeCode', namespaces))
+            self.type = util.testXMLAttribute(val, 'codeListValue')
+
+            self.thesaurus = None
+            val = md.find(util.nspath_eval('gmd:thesaurusName/gmd:CI_Citation', namespaces))
+            if val is not None:
+                self.thesaurus = {}
+
+                thesaurus = val.find(util.nspath_eval('gmd:title/gco:CharacterString', namespaces))
+                self.thesaurus['title'] = util.testXMLValue(thesaurus)
+
+                thesaurus = val.find(util.nspath_eval('gmd:date/gmd:CI_Date/gmd:date/gco:Date', namespaces))
+                self.thesaurus['date'] = util.testXMLValue(thesaurus)
+
+                thesaurus = val.find(util.nspath_eval('gmd:date/gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode', namespaces))
+                self.thesaurus['datetype'] = util.testXMLAttribute(thesaurus, 'codeListValue')
+
+
 class MD_DataIdentification(object):
     """ process MD_DataIdentification """
     def __init__(self, md=None, identtype=None):
@@ -241,6 +278,7 @@ class MD_DataIdentification(object):
             self.date = []
             self.datetype = []
             self.uselimitation = []
+            self.uselimitation_url = []
             self.accessconstraints = []
             self.classification = []
             self.otherconstraints = []
@@ -255,21 +293,24 @@ class MD_DataIdentification(object):
             self.contributor = []
             self.edition = None
             self.abstract = None
+            self.abstract_url = None
             self.purpose = None
             self.status = None
             self.contact = []
             self.keywords = []
+            self.keywords2 = []
             self.topiccategory = []
             self.supplementalinformation = None
             self.extent = None
             self.bbox = None
             self.temporalextent_start = None
             self.temporalextent_end = None
+            self.spatialrepresentationtype = []
         else:
             self.identtype = identtype
             val = md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString', namespaces))
             self.title = util.testXMLValue(val)
-            
+
             val = md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:alternateTitle/gco:CharacterString', namespaces))
             self.alternatetitle = util.testXMLValue(val)
 
@@ -290,28 +331,37 @@ class MD_DataIdentification(object):
 
             self.date = []
             self.datetype = []
-            
+
             for i in md.findall(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:date/gmd:CI_Date', namespaces)):
                 self.date.append(CI_Date(i))
-            
+
             self.uselimitation = []
+            self.uselimitation_url = []
             for i in md.findall(util.nspath_eval('gmd:resourceConstraints/gmd:MD_Constraints/gmd:useLimitation/gco:CharacterString', namespaces)):
                 val = util.testXMLValue(i)
                 if val is not None:
                     self.uselimitation.append(val)
-            
+
+            for i in md.findall(util.nspath_eval('gmd:resourceConstraints/gmd:MD_Constraints/gmd:useLimitation/gmx:Anchor', namespaces)):
+                val = util.testXMLValue(i)
+                val1 = i.attrib.get(util.nspath_eval('xlink:href', namespaces))
+
+                if val is not None:
+                    self.uselimitation.append(val)
+                    self.uselimitation_url.append(val1)
+
             self.accessconstraints = []
             for i in md.findall(util.nspath_eval('gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:accessConstraints/gmd:MD_RestrictionCode', namespaces)):
                 val = _testCodeListValue(i)
                 if val is not None:
                     self.accessconstraints.append(val)
-            
+
             self.classification = []
             for i in md.findall(util.nspath_eval('gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:accessConstraints/gmd:MD_ClassificationCode', namespaces)):
                 val = _testCodeListValue(i)
                 if val is not None:
                     self.classification.append(val)
-            
+
             self.otherconstraints = []
             for i in md.findall(util.nspath_eval('gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:otherConstraints/gco:CharacterString', namespaces)):
                 val = util.testXMLValue(i)
@@ -329,13 +379,13 @@ class MD_DataIdentification(object):
                 val = _testCodeListValue(i)
                 if val is not None:
                     self.useconstraints.append(val)
-            
+
             self.denominators = []
             for i in md.findall(util.nspath_eval('gmd:spatialResolution/gmd:MD_Resolution/gmd:equivalentScale/gmd:MD_RepresentativeFraction/gmd:denominator/gco:Integer', namespaces)):
                 val = util.testXMLValue(i)
                 if val is not None:
                     self.denominators.append(val)
-            
+
             self.distance = []
             self.uom = []
             for i in md.findall(util.nspath_eval('gmd:spatialResolution/gmd:MD_Resolution/gmd:distance/gco:Distance', namespaces)):
@@ -343,7 +393,7 @@ class MD_DataIdentification(object):
                 if val is not None:
                     self.distance.append(val)
                 self.uom.append(i.get("uom"))
-            
+
             self.resourcelanguage = []
             for i in md.findall(util.nspath_eval('gmd:language/gmd:LanguageCode', namespaces)):
                 val = _testCodeListValue(i)
@@ -371,6 +421,12 @@ class MD_DataIdentification(object):
             val = md.find(util.nspath_eval('gmd:abstract/gco:CharacterString', namespaces))
             self.abstract = util.testXMLValue(val)
 
+            val = md.find(util.nspath_eval('gmd:abstract/gmx:Anchor', namespaces))
+
+            if val is not None:
+                self.abstract = util.testXMLValue(val)
+                self.abstract_url = val.attrib.get(util.nspath_eval('xlink:href', namespaces))
+
             val = md.find(util.nspath_eval('gmd:purpose/gco:CharacterString', namespaces))
             self.purpose = util.testXMLValue(val)
 
@@ -380,7 +436,20 @@ class MD_DataIdentification(object):
             for i in md.findall(util.nspath_eval('gmd:pointOfContact/gmd:CI_ResponsibleParty', namespaces)):
                 o = CI_ResponsibleParty(i)
                 self.contact.append(o)
-            
+
+            self.spatialrepresentationtype = []
+            for val in md.findall(util.nspath_eval('gmd:spatialRepresentationType/gmd:MD_SpatialRepresentationTypeCode', namespaces)):
+                val = util.testXMLAttribute(val, 'codeListValue')
+                if val:
+                    self.spatialrepresentationtype.append(val)
+
+            warnings.warn(
+                'The .keywords and .keywords2 properties will merge into the '
+                '.keywords property in the future, with .keywords becoming a list '
+                'of MD_Keywords instances. This is currently implemented in .keywords2. '
+                'Please see https://github.com/geopython/OWSLib/issues/301 for more information',
+                FutureWarning)
+
             self.keywords = []
 
             for i in md.findall(util.nspath_eval('gmd:descriptiveKeywords', namespaces)):
@@ -403,21 +472,25 @@ class MD_DataIdentification(object):
                 for k in i.findall(util.nspath_eval('gmd:MD_Keywords/gmd:keyword', namespaces)):
                     val = k.find(util.nspath_eval('gco:CharacterString', namespaces))
                     if val is not None:
-                        val2 = util.testXMLValue(val) 
+                        val2 = util.testXMLValue(val)
                         if val2 is not None:
                             mdkw['keywords'].append(val2)
 
                 self.keywords.append(mdkw)
 
+            self.keywords2 = []
+            for mdkw in md.findall(util.nspath_eval('gmd:descriptiveKeywords/gmd:MD_Keywords', namespaces)):
+                self.keywords2.append(MD_Keywords(mdkw))
+
             self.topiccategory = []
             for i in md.findall(util.nspath_eval('gmd:topicCategory/gmd:MD_TopicCategoryCode', namespaces)):
                 val = util.testXMLValue(i)
                 if val is not None:
                     self.topiccategory.append(val)
-            
+
             val = md.find(util.nspath_eval('gmd:supplementalInformation/gco:CharacterString', namespaces))
             self.supplementalinformation = util.testXMLValue(val)
-            
+
             # There may be multiple geographicElement, create an extent
             # from the one containing either an EX_GeographicBoundingBox or EX_BoundingPolygon.
             # The schema also specifies an EX_GeographicDescription. This is not implemented yet.
@@ -447,7 +520,7 @@ class MD_DataIdentification(object):
                         val3 = extent.find(util.nspath_eval('gmd:EX_Extent/gmd:temporalElement/gmd:EX_TemporalExtent/gmd:extent/gml32:TimePeriod/gml32:endPosition', namespaces))
                     self.temporalextent_end = util.testXMLValue(val3)
 
-class MD_Distributor(object):        
+class MD_Distributor(object):
     """ process MD_Distributor """
     def __init__(self, md=None):
         if md is None:
@@ -489,7 +562,7 @@ class MD_Distribution(object):
             for ol in md.findall(util.nspath_eval('gmd:transferOptions/gmd:MD_DigitalTransferOptions/gmd:onLine/gmd:CI_OnlineResource', namespaces)):
                 self.online.append(CI_OnlineResource(ol))
 
-        
+
 class DQ_DataQuality(object):
     ''' process DQ_DataQuality'''
     def __init__(self, md=None):
@@ -499,6 +572,7 @@ class DQ_DataQuality(object):
             self.conformancedatetype = []
             self.conformancedegree = []
             self.lineage = None
+            self.lineage_url = None
             self.specificationtitle = None
             self.specificationdate = []
         else:
@@ -507,28 +581,33 @@ class DQ_DataQuality(object):
                 val = util.testXMLValue(i)
                 if val is not None:
                     self.conformancetitle.append(val)
-            
+
             self.conformancedate = []
             for i in md.findall(util.nspath_eval('gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:specification/gmd:CI_Citation/gmd:date/gmd:CI_Date/gmd:date/gco:Date', namespaces)):
                 val = util.testXMLValue(i)
                 if val is not None:
                     self.conformancedate.append(val)
-            
+
             self.conformancedatetype = []
             for i in md.findall(util.nspath_eval('gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:specification/gmd:CI_Citation/gmd:date/gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode', namespaces)):
                 val = _testCodeListValue(i)
                 if val is not None:
                     self.conformancedatetype.append(val)
-            
+
             self.conformancedegree = []
             for i in md.findall(util.nspath_eval('gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:pass/gco:Boolean', namespaces)):
                 val = util.testXMLValue(i)
                 if val is not None:
                     self.conformancedegree.append(val)
-            
+
             val = md.find(util.nspath_eval('gmd:lineage/gmd:LI_Lineage/gmd:statement/gco:CharacterString', namespaces))
             self.lineage = util.testXMLValue(val)
 
+            val = md.find(util.nspath_eval('gmd:lineage/gmd:LI_Lineage/gmd:statement/gmx:Anchor', namespaces))
+            if val is not None:
+                self.lineage = util.testXMLValue(val)
+                self.lineage_url = val.attrib.get(util.nspath_eval('xlink:href', namespaces))
+
             val = md.find(util.nspath_eval('gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:specification/gmd:CI_Citation/gmd:title/gco:CharacterString', namespaces))
             self.specificationtitle = util.testXMLValue(val)
 
@@ -554,21 +633,21 @@ class SV_ServiceIdentification(object):
             self.operations = []
             self.operateson = []
         else:
-            val=md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString', namespaces))
+            val = md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString', namespaces))
             self.title=util.testXMLValue(val)
-            
-            val=md.find(util.nspath_eval('gmd:abstract/gco:CharacterString', namespaces))
-            self.abstract=util.testXMLValue(val)
-            
+
+            val = md.find(util.nspath_eval('gmd:abstract/gco:CharacterString', namespaces))
+            self.abstract = util.testXMLValue(val)
+
             self.contact = None
             val = md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:citedResponsibleParty/gmd:CI_ResponsibleParty', namespaces))
             if val is not None:
                 self.contact = CI_ResponsibleParty(val)
-            
+
             self.identtype = 'service'
             val = md.find(util.nspath_eval('srv:serviceType/gco:LocalName', namespaces))
             self.type = util.testXMLValue(val)
-          
+
             val = md.find(util.nspath_eval('srv:serviceTypeVersion/gco:CharacterString', namespaces))
             self.version = util.testXMLValue(val)
 
@@ -594,16 +673,16 @@ class SV_ServiceIdentification(object):
                 for d in i.findall(util.nspath_eval('srv:SV_OperationMetadata/srv:DCP', namespaces)):
                     tmp2 = _testCodeListValue(d.find(util.nspath_eval('srv:DCPList', namespaces)))
                     tmp['dcplist'].append(tmp2)
-             
+
                 tmp['connectpoint'] = []
-     
+
                 for d in i.findall(util.nspath_eval('srv:SV_OperationMetadata/srv:connectPoint', namespaces)):
                     tmp3 = d.find(util.nspath_eval('gmd:CI_OnlineResource', namespaces))
                     tmp['connectpoint'].append(CI_OnlineResource(tmp3))
                 self.operations.append(tmp)
 
             self.operateson = []
-             
+
             for i in md.findall(util.nspath_eval('srv:operatesOn', namespaces)):
                 tmp = {}
                 tmp['uuidref'] = i.attrib.get('uuidref')
@@ -652,7 +731,7 @@ class EX_GeographicBoundingBox(object):
             self.miny = util.testXMLValue(val)
             val = md.find(util.nspath_eval('gmd:northBoundLatitude/gco:Decimal', namespaces))
             self.maxy = util.testXMLValue(val)
-    
+
 class EX_Polygon(object):
     def __init__(self, md=None):
         if md is None:
@@ -662,13 +741,13 @@ class EX_Polygon(object):
             linear_ring = md.find(util.nspath_eval('gml32:Polygon/gml32:exterior/gml32:LinearRing', namespaces))
             if linear_ring is not None:
                 self.exterior_ring = self._coordinates_for_ring(linear_ring)
-                        
+
             interior_ring_elements = md.findall(util.nspath_eval('gml32:Polygon/gml32:interior', namespaces))
             self.interior_rings = []
             for iring_element in interior_ring_elements:
                 linear_ring = iring_element.find(util.nspath_eval('gml32:LinearRing', namespaces))
                 self.interior_rings.append(self._coordinates_for_ring(linear_ring))
-            
+
     def _coordinates_for_ring(self, linear_ring):
         coordinates = []
         positions = linear_ring.findall(util.nspath_eval('gml32:pos', namespaces))
@@ -677,7 +756,7 @@ class EX_Polygon(object):
             coords = tuple([float(t) for t in tokens])
             coordinates.append(coords)
         return coordinates
-        
+
 class EX_GeographicBoundingPolygon(object):
     def __init__(self, md=None):
         if md is None:
@@ -686,13 +765,13 @@ class EX_GeographicBoundingPolygon(object):
         else:
             val = md.find(util.nspath_eval('gmd:extentTypeCode', namespaces))
             self.is_extent = util.testXMLValue(val)
-            
+
             md_polygons = md.findall(util.nspath_eval('gmd:polygon', namespaces))
-            
+
             self.polygons = []
             for val in md_polygons:
                 self.polygons.append(EX_Polygon(val))
-                
+
 class EX_Extent(object):
     """ process EX_Extent """
     def __init__(self, md=None):
@@ -708,11 +787,11 @@ class EX_Extent(object):
                 bboxElement = md.find(util.nspath_eval('gmd:EX_GeographicBoundingBox', namespaces))
                 if bboxElement is not None:
                     self.boundingBox = EX_GeographicBoundingBox(bboxElement)
-            
+
                 polygonElement = md.find(util.nspath_eval('gmd:EX_BoundingPolygon', namespaces))
                 if polygonElement is not None:
                     self.boundingPolygon = EX_GeographicBoundingPolygon(polygonElement)
-     
+
                 val = md.find(util.nspath_eval('gmd:EX_GeographicDescription/gmd:geographicIdentifier/gmd:MD_Identifier/gmd:code/gco:CharacterString', namespaces))
                 self.description_code = util.testXMLValue(val)
 
@@ -801,4 +880,3 @@ class CodelistCatalogue(object):
             return ids
         else:
             return None
-
diff --git a/owslib/util.py b/owslib/util.py
index 22bcaa7..21e821b 100644
--- a/owslib/util.py
+++ b/owslib/util.py
@@ -31,7 +31,6 @@ import cgi
 import re
 from copy import deepcopy
 import warnings
-import time
 import six
 import requests
 
@@ -489,16 +488,16 @@ def dump(obj, prefix=''):
 
     print("%s %s.%s : %s" % (prefix, obj.__module__, obj.__class__.__name__, obj.__dict__))
 
-def getTypedValue(type, value):
-    ''' Utility function to cast a string value to the appropriate XSD type. '''
-    
-    if type=='boolean':
-       return bool(value)
-    elif type=='integer':
-       return int(value)
-    elif type=='float':
+def getTypedValue(data_type, value):
+    '''Utility function to cast a string value to the appropriate XSD type. '''
+
+    if data_type == 'boolean':
+        return bool(value)
+    elif data_type == 'integer':
+        return int(value)
+    elif data_type == 'float':
         return float(value)
-    elif type=='string':
+    elif data_type == 'string':
         return str(value)
     else:
         return value # no type casting
@@ -537,10 +536,13 @@ def extract_xml_list(elements):
 Some people don't have seperate tags for their keywords and seperate them with
 a newline. This will extract out all of the keywords correctly.
 """
-    keywords = [re.split(r'[\n\r]+',f.text) for f in elements if f.text]
-    flattened = [item.strip() for sublist in keywords for item in sublist]
-    remove_blank = [_f for _f in flattened if _f]
-    return remove_blank
+    if elements:
+        keywords = [re.split(r'[\n\r]+',f.text) for f in elements if f.text]
+        flattened = [item.strip() for sublist in keywords for item in sublist]
+        remove_blank = [_f for _f in flattened if _f]
+        return remove_blank
+    else:
+        return []
 
 
 def bind_url(url):
@@ -594,5 +596,40 @@ def which_etree():
         which_etree = 'xml.etree'
     elif 'elementree' in etree.__file__:
         which_etree = 'elementtree.ElementTree'
-    
+
     return which_etree
+
+def findall(root, xpath, attribute_name=None, attribute_value=None):
+    """Find elements recursively from given root element based on
+    xpath and possibly given attribute
+
+    :param root: Element root element where to start search
+    :param xpath: xpath defintion, like {http://foo/bar/namespace}ElementName
+    :param attribute_name: name of possible attribute of given element
+    :param attribute_value: value of the attribute
+    :return: list of elements or None
+    """
+
+    found_elements = []
+
+
+    # python 2.6 < does not support complicated XPATH expressions used lower
+    if (2, 6) == sys.version_info[0:2] and which_etree() != 'lxml.etree':
+
+        elements = root.getiterator(xpath)
+
+        if attribute_name is not None and attribute_value is not None:
+            for element in elements:
+                if element.attrib.get(attribute_name) == attribute_value:
+                    found_elements.append(element)
+        else:
+            found_elements = elements
+    # python at least 2.7 and/or lxml can do things much simplier
+    else:
+        if attribute_name is not None and attribute_value is not None:
+            xpath = '%s[@%s="%s"]' % (xpath, attribute_name, attribute_value)
+        found_elements = root.findall('.//' + xpath)
+
+    if found_elements == []:
+        found_elements = None
+    return found_elements
diff --git a/owslib/wms.py b/owslib/wms.py
index 372a112..56ab50b 100644
--- a/owslib/wms.py
+++ b/owslib/wms.py
@@ -579,13 +579,6 @@ class ContentMetadata:
             self._children = value
         else:
             self._children.extend(value)
-        # If layer is a group and one of its children is queryable, the layer must be queryable.
-        if self._children and self.queryable == 0:
-            for child in self._children:
-                if child.queryable:
-                    self.queryable = child.queryable
-                    break
-
 
     def __str__(self):
         return 'Layer Name: %s Title: %s' % (self.name, self.title)
diff --git a/owslib/wmts.py b/owslib/wmts.py
index ec522f4..07142e8 100644
--- a/owslib/wmts.py
+++ b/owslib/wmts.py
@@ -196,7 +196,8 @@ class WebMapTileService(object):
 
         # serviceProvider metadata
         serviceprov = self._capabilities.find(_SERVICE_PROVIDER_TAG)
-        self.provider = ServiceProvider(serviceprov)
+        if serviceprov is not None:
+            self.provider = ServiceProvider(serviceprov)
 
         # serviceOperations metadata
         self.operations = []
diff --git a/owslib/wps.py b/owslib/wps.py
index 04cede2..5b74691 100644
--- a/owslib/wps.py
+++ b/owslib/wps.py
@@ -1,8 +1,8 @@
-############################################
+#
 #
 # Author: Luca Cinquini
 #
-############################################
+#
 
 """
 
@@ -20,25 +20,25 @@ More extensive testing is needed and feedback is appreciated.
 Usage
 -----
 
-The module can be used to execute three types of requests versus a remote WPS endpoint: 
+The module can be used to execute three types of requests versus a remote WPS endpoint:
 
-a) "GetCapabilities" 
+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
-    
+
 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
-    
+
 c) "Execute"
-    - 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 
+    - 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)
-    
-    - 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 
+
+    - 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
-    
+
     - 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:
@@ -48,31 +48,31 @@ c) "Execute"
                   - "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.
-          
+
     - 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.
-      
-      
+
+
 Examples
 --------
 
-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: 
+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
-    
+
 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
-    To execute a (fake) WPS invocation: 
+    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
-    
-The directory tests/ includes several doctest-style files wps_*.txt that show how to interactively submit a 
+
+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
@@ -87,10 +87,10 @@ Also, the directory tests/ contains several examples of well-formed "Execute" re
 from __future__ import (absolute_import, division, print_function)
 
 from owslib.etree import etree
-from owslib.ows import DEFAULT_OWS_NAMESPACE, ServiceIdentification, ServiceProvider, OperationsMetadata
+from owslib.ows import DEFAULT_OWS_NAMESPACE, ServiceIdentification, ServiceProvider, OperationsMetadata, BoundingBox
 from time import sleep
-from owslib.util import (testXMLValue, build_get_url, dump, getTypedValue, 
-                  getNamespace, element_to_string, nspath, openURL, nspath_eval, log)
+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
 try:                    # Python 3
@@ -114,9 +114,10 @@ DRAW_SCHEMA_LOCATION = 'http://cida.usgs.gov/climate/derivative/xsd/draw.xsd'
 WPS_DEFAULT_SCHEMA_LOCATION = 'http://schemas.opengis.net/wps/1.0.0/wpsExecute_request.xsd'
 WPS_DEFAULT_VERSION = '1.0.0'
 
+
 def get_namespaces():
-    ns = n.get_namespaces(["ogc","wfs","wps","gml","xsi","xlink"])
-    ns[None]  = n.get_namespace("wps")
+    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()
@@ -140,7 +141,7 @@ def is_literaldata(val):
     """
     is_str = isinstance(val, str)
     if not is_str:
-        # on python 2.x we need to check unicode 
+        # on python 2.x we need to check unicode
         try:
             is_str = isinstance(val, unicode)
         except:
@@ -157,169 +158,175 @@ def is_complexdata(val):
 
 
 class IComplexDataInput(object):
+
     """
     Abstract interface representing complex input object for a WPS request.
     """
-    
+
     def getXml(self):
         """
-        Method that returns the object data as an XML snippet, 
+        Method that returns the object data as an XML snippet,
         to be inserted into the WPS request document sent to the server.
         """
         raise NotImplementedError
-    
+
+
 class WebProcessingService(object):
+
     """
     Class that contains client-side functionality for invoking an OGC Web Processing Service (WPS).
-    
+
     Implements IWebProcessingService.
     """
-    
+
     def __init__(self, url, version=WPS_DEFAULT_VERSION, username=None, password=None, verbose=False, skip_caps=False):
         """
         Initialization method resets the object status.
-        By default it will execute a GetCapabilities invocation to the remote service, 
+        By default it will execute a GetCapabilities invocation to the remote service,
         which can be skipped by using skip_caps=True.
         """
-        
+
         # fields passed in from object initializer
         self.url = url
         self.username = username
         self.password = password
         self.version = version
         self.verbose = verbose
-                
+
         # fields populated by method invocations
         self._capabilities = None
         self.identification = None
         self.provider = None
-        self.operations=[]
-        self.processes=[]
+        self.operations = []
+        self.processes = []
 
         if not skip_caps:
             self.getcapabilities()
-        
+
     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.
         """
-        
+
         # read capabilities document
-        reader = WPSCapabilitiesReader(version=self.version, verbose=self.verbose)
+        reader = WPSCapabilitiesReader(
+            version=self.version, verbose=self.verbose)
         if xml:
             # read from stored XML file
             self._capabilities = reader.readFromString(xml)
         else:
-            self._capabilities = reader.readFromUrl(self.url, username=self.username, password=self.password)
-            
+            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)
-        
+
     def describeprocess(self, identifier, xml=None):
         """
         Requests a process document from a WPS service and populates the process metadata.
         Returns the process object.
         """
-        
+
         # read capabilities document
-        reader = WPSDescribeProcessReader(version=self.version, verbose=self.verbose)
+        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)
-            
+
         log.info(element_to_string(rootElement))
 
         # build metadata objects
         return self._parseProcessMetadata(rootElement)
-        
+
     def execute(self, identifier, inputs, output=None, request=None, response=None):
         """
-        Submits a WPS process execution request. 
+        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.
-        
+
         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
         """
-        
+
         # instantiate a WPSExecution object
         log.info('Executing WPS request...')
-        execution = WPSExecution(version=self.version, url=self.url, username=self.username, password=self.password, verbose=self.verbose)
+        execution = WPSExecution(version=self.version, url=self.url,
+                                 username=self.username, password=self.password, verbose=self.verbose)
 
-        # build XML request from parameters 
+        # build XML request from parameters
         if request is None:
-           requestElement = execution.buildRequest(identifier, inputs, output)
-           request = etree.tostring( requestElement )
-           execution.request = request   
+            requestElement = execution.buildRequest(identifier, inputs, output)
+            request = etree.tostring(requestElement)
+            execution.request = request
         log.debug(request)
-        
+
         # submit the request to the live server
-        if response is None:   
+        if response is None:
             response = execution.submitRequest(request)
         else:
             response = etree.fromstring(response)
-            
+
         log.debug(etree.tostring(response))
-            
+
         # parse response
         execution.parseResponse(response)
-                        
+
         return execution
-    
-        
+
     def _parseProcessMetadata(self, rootElement):
         """
         Method to parse a <ProcessDescriptions> XML element and returned the constructed Process object
         """
-        
-        processDescriptionElement = rootElement.find( 'ProcessDescription' )
+
+        processDescriptionElement = rootElement.find('ProcessDescription')
         process = Process(processDescriptionElement, verbose=self.verbose)
-    
+
         # override existing processes in object metadata, if existing already
         found = False
         for n, p in enumerate(self.processes):
-            if p.identifier==process.identifier:
-                self.processes[n]=process
+            if p.identifier == process.identifier:
+                self.processes[n] = process
                 found = True
         # otherwise add it
         if not found:
             self.processes.append(process)
-            
+
         return process
-                
-        
-    def _parseCapabilitiesMetadata(self, root):         
+
+    def _parseCapabilitiesMetadata(self, root):
         ''' Sets up capabilities metadata objects '''
-        
+
         # use the WPS namespace defined in the document root
         wpsns = getNamespace(root)
-        
+
         # loop over children WITHOUT requiring a specific namespace
         for element in root:
-            
+
             # thie element's namespace
             ns = getNamespace(element)
-            
+
             # <ows:ServiceIdentification> metadata
             if element.tag.endswith('ServiceIdentification'):
-                self.identification=ServiceIdentification(element, namespace=ns)
-                if self.verbose==True:
+                self.identification = ServiceIdentification(
+                    element, namespace=ns)
+                if self.verbose == True:
                     dump(self.identification)
-                    
+
             # <ows:ServiceProvider> metadata
             elif element.tag.endswith('ServiceProvider'):
-                self.provider=ServiceProvider(element, namespace=ns)  
-                if self.verbose==True:
+                self.provider = ServiceProvider(element, namespace=ns)
+                if self.verbose == True:
                     dump(self.provider)
-                    
+
             # <ns0:OperationsMetadata xmlns:ns0="http://www.opengeospatial.net/ows">
             #   <ns0:Operation name="GetCapabilities">
             #     <ns0:DCP>
@@ -331,11 +338,12 @@ class WebProcessingService(object):
             #  ........
             # </ns0:OperationsMetadata>
             elif element.tag.endswith('OperationsMetadata'):
-                for child in element.findall( nspath('Operation', ns=ns) ):
-                    self.operations.append( OperationsMetadata(child, namespace=ns) )
-                    if self.verbose==True:
+                for child in element.findall(nspath('Operation', ns=ns)):
+                    self.operations.append(
+                        OperationsMetadata(child, namespace=ns))
+                    if self.verbose == True:
                         dump(self.operations[-1])
-               
+
             # <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>
@@ -344,23 +352,23 @@ class WebProcessingService(object):
             #   ......
             # </wps:ProcessOfferings>
             elif element.tag.endswith('ProcessOfferings'):
-                for child in element.findall( nspath('Process', ns=ns) ):
+                for child in element.findall(nspath('Process', ns=ns)):
                     p = Process(child, verbose=self.verbose)
                     self.processes.append(p)
-                    if self.verbose==True:
+                    if self.verbose == True:
                         dump(self.processes[-1])
 
-                   
-        
+
 class WPSReader(object):
+
     """
     Superclass for reading a WPS document into a lxml.etree infoset.
     """
 
-    def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):        
+    def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):
         self.version = version
         self.verbose = verbose
-                
+
     def _readFromUrl(self, url, data, method='Get', username=None, password=None):
         """
         Method to get and parse a WPS document, returning an elementtree instance.
@@ -368,110 +376,123 @@ class WPSReader(object):
         data: GET: dictionary of HTTP (key, value) parameter pairs, POST: XML document to post
         username, password: optional user credentials
         """
-        
+
         if method == 'Get':
             # full HTTP request url
             request_url = build_get_url(url, data)
             log.debug(request_url)
-    
+
             # split URL into base url and query string to use utility function
-            spliturl=request_url.split('?')
-            u = openURL(spliturl[0], spliturl[1], method='Get', username=username, password=password)
+            spliturl = request_url.split('?')
+            u = openURL(spliturl[0], spliturl[
+                        1], method='Get', username=username, password=password)
             return etree.fromstring(u.read())
-        
+
         elif method == 'Post':
-            u = openURL(url, data, method='Post', username = username, password = password)
+            u = openURL(url, data, method='Post',
+                        username=username, password=password)
             return etree.fromstring(u.read())
-            
+
         else:
             raise Exception("Unrecognized HTTP method: %s" % method)
-                
-        
+
     def readFromString(self, string):
         """
         Method to read a WPS GetCapabilities document from an XML string.
         """
-        
+
         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)    
+            raise ValueError(
+                "Input must be of type string, not %s" % type(string))
+        return etree.fromstring(string)
+
 
 class WPSCapabilitiesReader(WPSReader):
+
     """
     Utility class that reads and parses a WPS GetCapabilities document into a lxml.etree infoset.
     """
-    
+
     def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):
         # superclass initializer
-        super(WPSCapabilitiesReader,self).__init__(version=version, verbose=verbose)
-        
+        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
         """
-        return self._readFromUrl(url, 
-                                 {'service':'WPS', 'request':'GetCapabilities', 'version':self.version}, 
+        return self._readFromUrl(url,
+                                 {'service': 'WPS', 'request':
+                                     'GetCapabilities', 'version': self.version},
                                  username=username, password=password)
-            
+
+
 class WPSDescribeProcessReader(WPSReader):
+
     """
     Class that reads and parses a WPS DescribeProcess document into a etree infoset
     """
 
     def __init__(self, version=WPS_DEFAULT_VERSION, verbose=False):
         # superclass initializer
-        super(WPSDescribeProcessReader,self).__init__(version=version, verbose=verbose)
+        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'.
         """
-        
-        return self._readFromUrl(url, 
-                                 {'service':'WPS', 'request':'DescribeProcess', 'version':self.version, 'identifier':identifier}, 
+
+        return self._readFromUrl(url,
+                                 {'service': 'WPS', 'request': 'DescribeProcess',
+                                     'version': self.version, 'identifier': identifier},
                                  username=username, password=password)
-        
+
+
 class WPSExecuteReader(WPSReader):
+
     """
     Class that reads and parses a WPS Execute response document into a etree infoset
     """
+
     def __init__(self, verbose=False):
         # superclass initializer
-        super(WPSExecuteReader,self).__init__(verbose=verbose)
-        
+        super(WPSExecuteReader, self).__init__(verbose=verbose)
+
     def readFromUrl(self, url, data={}, method='Get', username=None, password=None):
-         """
-         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)
-
-    
+        """
+        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():
+
     """
     Class that represents a single WPS process executed on a remote WPS service.
     """
-    
+
     def __init__(self, version=WPS_DEFAULT_VERSION, url=None, username=None, password=None, verbose=False):
-        
+
         # initialize fields
         self.url = url
         self.version = version
         self.username = username
         self.password = password
         self.verbose = verbose
-        
+
         # request document
         self.request = None
-        
+
         # last response document
         self.response = None
-        
+
         # status fields retrieved from the response documents
         self.process = None
         self.serviceInstance = None
@@ -480,10 +501,9 @@ class WPSExecution():
         self.statusMessage = None
         self.errors = []
         self.statusLocation = None
-        self.dataInputs=[]
-        self.processOutputs=[]
-        
-        
+        self.dataInputs = []
+        self.processOutputs = []
+
     def buildRequest(self, identifier, inputs=[], output=None):
         """
         Method to build a WPS process request.
@@ -494,32 +514,38 @@ class WPSExecution():
               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
         """
-        
-        #<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">       
+
+        #<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)
-        root.set(nspath_eval('xsi:schemaLocation', namespaces), '%s %s' % (namespaces['wps'], WPS_DEFAULT_SCHEMA_LOCATION) )
-        
+        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>
-        identifierElement = etree.SubElement(root, nspath_eval('ows:Identifier', namespaces))
+        identifierElement = etree.SubElement(
+            root, nspath_eval('ows:Identifier', namespaces))
         identifierElement.text = identifier
-        
+
         # <wps:DataInputs>
-        dataInputsElement = etree.SubElement(root, nspath_eval('wps:DataInputs', namespaces))
-        
-        for (key,val) in inputs:
+        dataInputsElement = etree.SubElement(
+            root, nspath_eval('wps:DataInputs', namespaces))
+
+        for (key, val) in inputs:
 
-            inputElement = etree.SubElement(dataInputsElement, nspath_eval('wps:Input', namespaces))
-            identifierElement = etree.SubElement(inputElement, nspath_eval('ows:Identifier', namespaces))
+            inputElement = etree.SubElement(
+                dataInputsElement, nspath_eval('wps:Input', namespaces))
+            identifierElement = etree.SubElement(
+                inputElement, nspath_eval('ows:Identifier', namespaces))
             identifierElement.text = key
-            
+
             # Literal data
             # <wps:Input>
             #   <ows:Identifier>DATASET_URI</ows:Identifier>
@@ -529,10 +555,12 @@ class WPSExecution():
             # </wps:Input>
             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))
+                dataElement = etree.SubElement(
+                    inputElement, nspath_eval('wps:Data', namespaces))
+                literalDataElement = etree.SubElement(
+                    dataElement, nspath_eval('wps:LiteralData', namespaces))
                 literalDataElement.text = val
-                
+
             # Complex data
             # <wps:Input>
             #   <ows:Identifier>FEATURE_COLLECTION</ows:Identifier>
@@ -552,10 +580,11 @@ class WPSExecution():
             # </wps:Input>
             elif is_complexdata(val):
                 log.debug("complexdata %s", key)
-                inputElement.append( val.getXml() )
+                inputElement.append(val.getXml())
             else:
-                raise Exception('input type of "%s" parameter is unknown' % key)
-        
+                raise Exception(
+                    'input type of "%s" parameter is unknown' % key)
+
         # <wps:ResponseForm>
         #   <wps:ResponseDocument storeExecuteResponse="true" status="true">
         #     <wps:Output asReference="true">
@@ -564,160 +593,172 @@ class WPSExecution():
         #   </wps:ResponseDocument>
         # </wps:ResponseForm>
         if output is not None:
-            responseFormElement = etree.SubElement(root, nspath_eval('wps:ResponseForm', namespaces))
-            responseDocumentElement = etree.SubElement(responseFormElement, nspath_eval('wps:ResponseDocument', namespaces), 
-                                                       attrib={'storeExecuteResponse':'true', 'status':'true'} )
+            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):
-                self._add_output(responseDocumentElement, output, asReference=True)
+                self._add_output(
+                    responseDocumentElement, output, asReference=True)
             elif isinstance(output, list):
-                for (identifier,as_reference) in output:
-                    self._add_output(responseDocumentElement, identifier, asReference=as_reference)
+                for (identifier, as_reference) in output:
+                    self._add_output(
+                        responseDocumentElement, identifier, asReference=as_reference)
             else:
-                raise Exception('output parameter is neither string nor list. output=%s' % output)
+                raise Exception(
+                    'output parameter is neither string nor list. output=%s' % output)
         return root
 
     def _add_output(self, element, identifier, asReference=False):
-        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
-                  
-                
+        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.
-        
+
         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.
         """
-                    
+
         reader = WPSExecuteReader(verbose=self.verbose)
         if response is None:
             # override status location
             if url is not None:
                 self.statusLocation = url
-            log.info('\nChecking execution status... (location=%s)' % self.statusLocation)
-            response = reader.readFromUrl(self.statusLocation, username=self.username, password=self.password)
+            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)
-                
+
         # store latest response
         self.response = etree.tostring(response)
         log.debug(self.response)
 
         self.parseResponse(response)
-                    
+
         # sleep given number of seconds
-        if self.isComplete()==False:
+        if self.isComplete() == False:
             log.info('Sleeping %d seconds...' % sleepSecs)
             sleep(sleepSecs)
 
-        
     def getStatus(self):
         return self.status
-        
+
     def isComplete(self):
-        if (self.status=='ProcessSucceeded' or self.status=='ProcessFailed' or self.status=='Exception'):
+        if (self.status == 'ProcessSucceeded' or self.status == 'ProcessFailed' or self.status == 'Exception'):
             return True
-        elif (self.status=='ProcessStarted'):
+        elif (self.status == 'ProcessStarted'):
             return False
-        elif (self.status=='ProcessAccepted' or self.status=='ProcessPaused'):
+        elif (self.status == 'ProcessAccepted' or self.status == 'ProcessPaused'):
             return False
         else:
-            raise Exception('Unknown process execution status: %s' % self.status)
-        
+            raise Exception(
+                'Unknown process execution status: %s' % self.status)
+
     def isSucceded(self):
-         if self.status=='ProcessSucceeded':
-             return True
-         else:
-             return False
-        
+        if self.status == 'ProcessSucceeded':
+            return True
+        else:
+            return False
+
     def isNotComplete(self):
         return not self.isComplete()
-        
+
     def getOutput(self, filepath=None):
         """
-        Method to write the outputs of a WPS process to a file: 
+        Method to write the outputs of a WPS process to a file:
         either retrieves the referenced files from the server, or writes out the content of response embedded output.
-        
-        filepath: optional path to the output file, otherwise a file will be created in the local directory with the name assigned by the server, 
+
+        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.
         """
-        
+
         if self.isSucceded():
             content = ''
             for output in self.processOutputs:
-                
-                output_content = output.retrieveData(self.username, self.password)
-                
+
+                output_content = output.retrieveData(
+                    self.username, self.password)
+
                 # ExecuteResponse contains reference to server-side output
                 if output_content is not "":
                     content = content + output_content
                     if filepath is None:
                         filepath = output.fileName
-                    
-                # ExecuteResponse contain embedded output   
-                if len(output.data)>0:
+
+                # ExecuteResponse contain embedded output
+                if len(output.data) > 0:
                     if filepath is None:
                         filepath = 'wps.out'
                     for data in output.data:
                         content = content + data
-                    
+
             # write out content
             if content is not '':
                 out = open(filepath, 'wb')
                 out.write(content)
                 out.close()
-                log.info('Output written to file: %s' %filepath)
-            
+                log.info('Output written to file: %s' % filepath)
+
         else:
-            raise Exception("Execution not successfully completed: status=%s" % self.status)
-    
+            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.
-        
+
         request: the XML request document to be submitted as POST to the server.
-        """ 
-        
+        """
+
         self.request = request
         reader = WPSExecuteReader(verbose=self.verbose)
-        response = reader.readFromUrl(self.url, request, method='Post', username=self.username, password=self.password)
+        response = reader.readFromUrl(
+            self.url, request, method='Post', username=self.username, password=self.password)
         self.response = response
         return response
- 
-        '''       
+
+        '''
         if response is None:
             # override status location
             if url is not None:
                 self.statusLocation = url
-            
+
         else:
             response = reader.readFromString(response)
 
-        
+
         '''
-    
+
     def parseResponse(self, response):
         """
         Method to parse a WPS response document
         """
-    
+
         rootTag = response.tag.split('}')[1]
         # <ns0:ExecuteResponse>
         if rootTag == 'ExecuteResponse':
             self._parseExecuteResponse(response)
-                    
+
         # <ows:ExceptionReport>
         elif rootTag == 'ExceptionReport':
             self._parseExceptionReport(response)
-            
+
         else:
             log.debug('Unknown Response')
-            
+
         # log status, errors
         log.info('Execution status=%s' % self.status)
         log.info('Percent completed=%s' % self.percentCompleted)
@@ -725,7 +766,6 @@ class WPSExecution():
         for error in self.errors:
             dump(error)
 
-            
     def _parseExceptionReport(self, root):
         """
         Method to parse a WPS ExceptionReport document and populate this object's metadata.
@@ -733,23 +773,22 @@ class WPSExecution():
         # set exception status, unless set already
         if self.status is None:
             self.status = "Exception"
-            
-        for exceptionEl in root.findall( nspath('Exception', ns=namespaces['ows']) ):
-            self.errors.append( WPSException(exceptionEl) )
 
+        for exceptionEl in root.findall(nspath('Exception', ns=namespaces['ows'])):
+            self.errors.append(WPSException(exceptionEl))
 
-    def _parseExecuteResponse(self, root):      
+    def _parseExecuteResponse(self, root):
         """
         Method to parse a WPS ExecuteResponse response document and populate this object's metadata.
         """
-        
+
         # retrieve WPS namespace directly from root element
         wpsns = getNamespace(root)
 
-        self.serviceInstance = root.get( 'serviceInstance' )
+        self.serviceInstance = root.get('serviceInstance')
         if self.statusLocation is None:
-            self.statusLocation = root.get( 'statusLocation' )
-        
+            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>
@@ -763,7 +802,7 @@ class WPSExecution():
         #   </ows:ExceptionReport>
         #  </ns0:ProcessFailed>
         # </ns0:Status>
-        statusEl = root.find( nspath('Status/*', ns=wpsns) )
+        statusEl = root.find(nspath('Status/*', ns=wpsns))
         self.status = statusEl.tag.split('}')[1]
         # get progress info
         try:
@@ -778,82 +817,89 @@ class WPSExecution():
             if element.tag.endswith('ExceptionReport'):
                 self._parseExceptionReport(element)
 
-        self.process = Process(root.find(nspath('Process', ns=wpsns)), verbose=self.verbose)
-        
+        self.process = Process(
+            root.find(nspath('Process', ns=wpsns)), verbose=self.verbose)
+
         #<wps:DataInputs 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">
-        for inputElement in root.findall( nspath('DataInputs/Input', ns=wpsns) ):
-            self.dataInputs.append( Input(inputElement) )
-            if self.verbose==True:
+        # 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])
-        
+
         # <ns:ProcessOutputs>
-        # 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:
+        # 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])
-            
+
+
 class ComplexData(object):
+
     """
     Class that represents a ComplexData element in a WPS document
     """
-    
+
     def __init__(self, mimeType=None, encoding=None, schema=None):
         self.mimeType = mimeType
         self.encoding = encoding
         self.schema = schema
 
+
 class InputOutput(object):
+
     """
     Superclass of a WPS input or output data object.
     """
-    
+
     def __init__(self, element):
-                
+
         self.abstract = None
 
         # loop over sub-elements without requiring a specific namespace
         for subElement in element:
-            
+
             # <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">SUMMARIZE_TIMESTEP</ows:Identifier>
             if subElement.tag.endswith('Identifier'):
-                self.identifier = testXMLValue( subElement )
+                self.identifier = testXMLValue(subElement)
 
             # <ows:Title xmlns:ows="http://www.opengis.net/ows/1.1">Summarize Timestep</ows:Title>
             elif subElement.tag.endswith('Title'):
-                self.title = testXMLValue( subElement )
-        
+                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'):
-                self.abstract = testXMLValue( subElement )
-                
+                self.abstract = testXMLValue(subElement)
+
         self.allowedValues = []
         self.supportedValues = []
         self.defaultValue = None
         self.dataType = None
         self.anyValue = False
-        
+
     def _parseData(self, element):
         """
         Method to parse a "Data" element
         """
-        
+
         # <ns0:Data>
         #        <ns0:ComplexData mimeType="text/plain">
-        #             7504912.93758151 -764109.175074507,7750849.82379226 -22141.8611641468,8561828.42371234 -897195.923493867,7724946.16844165 -602984.014261927 
+        #             7504912.93758151 -764109.175074507,7750849.82379226 -22141.8611641468,8561828.42371234 -897195.923493867,7724946.16844165 -602984.014261927
         #        </ns0:ComplexData>
         # </ns0:Data>
-        #nspath('Data', ns=WPS_NAMESPACE)
-        complexDataElement = element.find( nspath('ComplexData', ns=getNamespace(element)) )
-        if complexDataElement is not None:
+        # 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"
-        
+
     def _parseLiteralData(self, element, literalElementName):
         """
         Method to parse the LiteralData element.
         """
-        
+
         # <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">
@@ -863,32 +909,42 @@ class InputOutput(object):
         #    </ows:AllowedValues>
         #    <DefaultValue>COMMA</DefaultValue>
         # </LiteralData>
-        
+
         # <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>
-        literalDataElement = element.find( literalElementName )
-        if literalDataElement is not None:
+
+        literal_data_element = element.find(literalElementName)
+
+        if literal_data_element is not None:
             self.dataType = 'LiteralData'
-            for subElement in literalDataElement:
-                subns = getNamespace(subElement)
-                if subElement.tag.endswith('DataType'):
-                    self.dataType = subElement.get( nspath("reference", ns=subns) ).split(':')[1]
-                elif subElement.tag.endswith('AllowedValues'):
-                    for value in subElement.findall( nspath('Value', ns=subns) ):
-                        self.allowedValues.append( getTypedValue(self.dataType, value.text) )
-                elif subElement.tag.endswith('DefaultValue'):
-                    self.defaultValue = getTypedValue(self.dataType, subElement.text)
-                elif subElement.tag.endswith('AnyValue'):
+            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.
         """
-        
+
         # <ComplexData>
         #     <Default>
         #         <Format>
@@ -918,100 +974,158 @@ class InputOutput(object):
         #         <Schema>NONE</Schema>
         #     </SupportedComplexData>
         # </ComplexOutput>
-        
-        complexDataElement = element.find( complexDataElementName )
-        if complexDataElement is not None:
+
+        complex_data_element = element.find(complexDataElementName)
+        if complex_data_element is not None:
             self.dataType = "ComplexData"
-            
-            for supportedComlexDataElement in complexDataElement.findall( 'SupportedComplexData' ):
-                self.supportedValues.append( ComplexData( mimeType=testXMLValue( supportedComlexDataElement.find( 'Format' ) ),
-                                                          encoding=testXMLValue( supportedComlexDataElement.find( 'Encoding' ) ),
-                                                          schema=testXMLValue( supportedComlexDataElement.find( 'Schema' ) ) 
-                                                         ) 
+
+            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'))
+                    )
                 )
-                
-            for formatElement in complexDataElement.findall( 'Supported/Format'):
-                self.supportedValues.append( ComplexData( mimeType=testXMLValue( formatElement.find( 'MimeType' ) ),
-                                                          encoding=testXMLValue( formatElement.find( 'Encoding' ) ),
-                                                          schema=testXMLValue( formatElement.find( 'Schema' ) ) 
-                                                         ) 
+
+            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'))
+                    )
+                )
+
+            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'))
                 )
-               
-            defaultFormatElement = complexDataElement.find( 'Default/Format' ) 
-            if defaultFormatElement is not None:
-                self.defaultValue = ComplexData( mimeType=testXMLValue( defaultFormatElement.find( 'MimeType' ) ),
-                                                 encoding=testXMLValue( defaultFormatElement.find( 'Encoding' ) ),
-                                                 schema=testXMLValue( defaultFormatElement.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.
     """
-    
+
     def __init__(self, inputElement):
-        
+
         # superclass initializer
-        super(Input,self).__init__(inputElement)
-        
+        super(Input, self).__init__(inputElement)
+
         # <Input maxOccurs="1" minOccurs="0">
         # OR
         # <MinimumOccurs>1</MinimumOccurs>
         self.minOccurs = -1
         if inputElement.get("minOccurs") is not None:
-            self.minOccurs = int( inputElement.get("minOccurs") )
+            self.minOccurs = int(inputElement.get("minOccurs"))
         if inputElement.find('MinimumOccurs') is not None:
-            self.minOccurs = int( testXMLValue( inputElement.find('MinimumOccurs') ) )  
+            self.minOccurs = int(
+                testXMLValue(inputElement.find('MinimumOccurs')))
         self.maxOccurs = -1
         if inputElement.get("maxOccurs") is not None:
-            self.maxOccurs = int( inputElement.get("maxOccurs") )
+            self.maxOccurs = int(inputElement.get("maxOccurs"))
         if inputElement.find('MaximumOccurs') is not None:
-            self.maxOccurs = int( testXMLValue( inputElement.find('MaximumOccurs') ) )       
-        
+            self.maxOccurs = int(
+                testXMLValue(inputElement.find('MaximumOccurs')))
+
         # <LiteralData>
         self._parseLiteralData(inputElement, 'LiteralData')
-                
+
         # <ComplexData>
         self._parseComplexData(inputElement, 'ComplexData')
-                
-    
+
+        # <BoundingBoxData>
+        self._parseBoundingBoxData(inputElement, 'BoundingBoxData')
+
+
 class Output(InputOutput):
+
     """
     Class that represents a WPS process output.
     """
-    
+
     def __init__(self, outputElement):
-    
+
         # superclass initializer
-        super(Output,self).__init__(outputElement)
-        
+        super(Output, self).__init__(outputElement)
+
         self.reference = None
         self.mimeType = None
         self.data = []
         self.fileName = None
         self.filePath = None
-        
+
         # extract wps namespace from outputElement itself
         wpsns = getNamespace(outputElement)
-        
+
         # <ns:Reference encoding="UTF-8" mimeType="text/csv"
-        #     href="http://cida.usgs.gov/climate/gdp/process/RetrieveResultServlet?id=1318528582026OUTPUT.601bb3d0-547f-4eab-8642-7c7d2834459e" />
-        referenceElement = outputElement.find( nspath('Reference', ns=wpsns) )
+        # 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')
-       
+
         # <LiteralOutput>
         self._parseLiteralData(outputElement, 'LiteralOutput')
         
         # <ComplexData> or <ComplexOutput>
         self._parseComplexData(outputElement, 'ComplexOutput')
-        
-        # <Data>   
+
+        # <BoundingBoxData>
+        self._parseBoundingBoxData(outputElement, 'BoundingBoxOutput')
+
+        # <Data>
         # <ns0:Data>
         #        <ns0:ComplexData mimeType="text/plain">
-        #             7504912.93758151 -764109.175074507,7750849.82379226 -22141.8611641468,8561828.42371234 -897195.923493867,7724946.16844165 -602984.014261927 
+        #             7504912.93758151 -764109.175074507,7750849.82379226 -22141.8611641468,8561828.42371234 -897195.923493867,7724946.16844165 -602984.014261927
         #        </ns0:ComplexData>
         # </ns0:Data>
         # OR:
@@ -1023,7 +1137,7 @@ class Output(InputOutput):
         #                     <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>
-        #            </gml:boundedBy>                         
+        #            </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.33 [...]
@@ -1040,9 +1154,21 @@ class Output(InputOutput):
         #        </ns3:FeatureCollection>
         #     </ns0:ComplexData>
         # </ns0:Data>
-        dataElement = outputElement.find( nspath('Data', ns=wpsns) )    
+        #
+        #
+        # 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:
-            complexDataElement = dataElement.find( nspath('ComplexData', ns=wpsns) )
+            complexDataElement = dataElement.find(
+                nspath('ComplexData', ns=wpsns))
             if complexDataElement is not None:
                 self.dataType = "ComplexData"
                 self.mimeType = complexDataElement.get('mimeType')
@@ -1050,58 +1176,73 @@ class Output(InputOutput):
                     self.data.append(complexDataElement.text.strip())
                 for child in complexDataElement:
                     self.data.append(etree.tostring(child))
-            literalDataElement = dataElement.find( nspath('LiteralData', ns=wpsns) )
+            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())
-                    
+            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)
+
     def retrieveData(self, username=None, password=None):
         """
-        Method to retrieve data from server-side reference: 
+        Method to retrieve data from server-side reference:
         returns "" if the reference is not known.
-        
-        username, password: credentials to access the remote WPS server 
+
+        username, password: credentials to access the remote WPS server
         """
-        
+
         url = self.reference
-        if url is None: 
+        if url is None:
             return ""
-        
+
         # 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'
         log.info('Output URL=%s' % url)
         if '?' in url:
-            spliturl=url.split('?')
-            u = openURL(spliturl[0], spliturl[1], method='Get', username = username, password = password)
+            spliturl = url.split('?')
+            u = openURL(spliturl[0], spliturl[
+                        1], method='Get', username=username, password=password)
             # extract output filepath from URL query string
             self.fileName = spliturl[1].split('=')[1]
         else:
-            u = openURL(url, '', method='Get', username = username, password = password)
+            u = openURL(
+                url, '', method='Get', username=username, password=password)
             # extract output filepath from base URL
             self.fileName = url.split('/')[-1]
-                
-        return u.read()      
 
-                    
+        return u.read()
+
     def writeToDisk(self, path=None, username=None, password=None):
         """
-        Method to write an output of a WPS process to disk: 
+        Method to write an output of a WPS process to disk:
         it either retrieves the referenced file from the server, or write out the content of response embedded output.
-        
+
         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
-        """ 
-        
-        # Check if ExecuteResponse contains reference to server-side output    
+        """
+
+        # Check if ExecuteResponse contains reference to server-side output
         content = self.retrieveData(username, password)
-         
-        # ExecuteResponse contain embedded output   
-        if content is "" and len(self.data)>0:
+
+        # ExecuteResponse contain embedded output
+        if content is "" and len(self.data) > 0:
             self.fileName = self.identifier
             for data in self.data:
                 content = content + data
-                    
+
         # write out content
         if content is not "":
             if self.fileName == "":
@@ -1110,87 +1251,92 @@ class Output(InputOutput):
             out = open(self.filePath, 'wb')
             out.write(content)
             out.close()
-            log.info('Output written to file: %s' %self.filePath)
-                
-                    
+            log.info('Output written to file: %s' % self.filePath)
+
+
 class WPSException:
+
     """
     Class representing an exception raised by a WPS.
     """
-    
+
     def __init__(self, root):
         self.code = root.attrib.get("exceptionCode", None)
         self.locator = root.attrib.get("locator", None)
-        textEl = root.find( nspath('ExceptionText', ns=getNamespace(root)) )
+        textEl = root.find(nspath('ExceptionText', ns=getNamespace(root)))
         if textEl is not None:
             self.text = textEl.text
         else:
             self.text = ""
 
+
 class Process(object):
+
     """
     Class that represents a WPS process.
     """
-    
+
     def __init__(self, elem, verbose=False):
         """ Initialization method extracts all available metadata from an XML document (passed in as etree object) """
-        
-        # <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" 
+
+        # <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
-        
+
         wpsns = getNamespace(elem)
-        
+
         # <ProcessDescription statusSupported="true" storeSupported="true" ns0:processVersion="1.0.0">
-        self.processVersion = elem.get( nspath('processVersion', ns=wpsns) )
-        self.statusSupported = bool( elem.get( "statusSupported" ) )
-        self.storeSupported = bool( elem.get( "storeSupported" ) )
+        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:
 
             # this element's namespace
             ns = getNamespace(child)
-            
+
             # <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'):
-                self.identifier = testXMLValue( child )
-        
+                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'):
-                self.title =  testXMLValue( child )
-        
+                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  [...]
             elif child.tag.endswith('Abstract'):
-                self.abstract = testXMLValue( child )
-        
-        if self.verbose==True:
+                self.abstract = testXMLValue(child)
+
+        if self.verbose == True:
             dump(self)
-        
+
         # <DataInputs>
         self.dataInputs = []
-        for inputElement in elem.findall( 'DataInputs/Input' ):
-            self.dataInputs.append( Input(inputElement) )
-            if self.verbose==True:
+        for inputElement in elem.findall('DataInputs/Input'):
+            self.dataInputs.append(Input(inputElement))
+            if self.verbose == True:
                 dump(self.dataInputs[-1], prefix='\tInput: ')
-        
+
         # <ProcessOutputs>
         self.processOutputs = []
-        for outputElement in elem.findall( 'ProcessOutputs/Output' ):
-            self.processOutputs.append( Output(outputElement) )
-            if self.verbose==True:
+        for outputElement in elem.findall('ProcessOutputs/Output'):
+            self.processOutputs.append(Output(outputElement))
+            if self.verbose == True:
                 dump(self.processOutputs[-1],  prefix='\tOutput: ')
-     
+
 
 class ComplexDataInput(IComplexDataInput, ComplexData):
+
     def __init__(self, value, mimeType=None, encoding=None, schema=None):
-        super(ComplexDataInput, self).__init__(mimeType=mimeType, encoding=encoding, schema=schema)
+        super(ComplexDataInput, self).__init__(
+            mimeType=mimeType, encoding=encoding, schema=schema)
         self.value = value
-        
+
     def getXml(self):
         if is_reference(self.value):
             return self.complexDataAsReference()
@@ -1202,7 +1348,7 @@ class ComplexDataInput(IComplexDataInput, ComplexData):
            <wps:Reference xlink:href="http://somewhere/test.xml"/>
         """
         refElement = etree.Element(nspath_eval('wps:Reference', namespaces),
-                                attrib = { nspath_eval("xlink:href",namespaces) : self.value} )
+                                   attrib={nspath_eval("xlink:href", namespaces): self.value})
         return refElement
 
     def complexDataRaw(self):
@@ -1222,31 +1368,36 @@ class ComplexDataInput(IComplexDataInput, ComplexData):
             attrib['schema'] = self.schema
         if self.mimeType:
             attrib['mimeType'] = self.mimeType
-        complexDataElement = etree.SubElement(dataElement, nspath_eval('wps:ComplexData', namespaces), attrib=attrib )
+        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.
-    
+
     Implements IComplexDataInput.
     '''
-    
+
     def __init__(self):
         pass
-    
+
     def getXml(self):
         raise NotImplementedError
-    
+
+
 class WFSFeatureCollection(FeatureCollection):
+
     '''
     FeatureCollection specified by a WFS query.
     All subclasses must implement the getQuery() method to provide the specific query portion of the XML.
     '''
-    
+
     def __init__(self, wfsUrl, wfsQuery):
         '''
         wfsUrl: the WFS service URL
@@ -1255,7 +1406,7 @@ class WFSFeatureCollection(FeatureCollection):
         '''
         self.url = wfsUrl
         self.query = wfsQuery
-    
+
     #    <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">
@@ -1264,15 +1415,18 @@ class WFSFeatureCollection(FeatureCollection):
     #      </wps:Body>
     #   </wps:Reference>
     def getXml(self):
-        
-        root = etree.Element(nspath_eval('wps:Reference', namespaces), attrib = { nspath_eval("xlink:href",namespaces) : self.url} )
-        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/1.1.0/WFS.xsd')})
-        
+
+        root = etree.Element(nspath_eval('wps:Reference', namespaces),
+                             attrib={nspath_eval("xlink:href", namespaces): self.url})
+        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/1.1.0/WFS.xsd')})
+
         #            <wfs:Query typeName="sample:CONUS_States">
         #                <wfs:PropertyName>the_geom</wfs:PropertyName>
         #                <wfs:PropertyName>STATE</wfs:PropertyName>
@@ -1280,24 +1434,26 @@ class WFSFeatureCollection(FeatureCollection):
         #                    <ogc:GmlObjectId gml:id="CONUS_States.508"/>
         #                </ogc:Filter>
         #            </wfs:Query>
-        getFeatureElement.append( self.query.getXml() )
-        
+        getFeatureElement.append(self.query.getXml())
+
         return root
-    
+
+
 class WFSQuery(IComplexDataInput):
+
     '''
     Class representing a WFS query, for insertion into a WFSFeatureCollection instance.
-    
+
     Implements IComplexDataInput.
     '''
-    
+
     def __init__(self, typeName, propertyNames=[], filters=[]):
         self.typeName = typeName
         self.propertyNames = propertyNames
         self.filters = filters
-        
+
     def getXml(self):
-        
+
         #            <wfs:Query typeName="sample:CONUS_States">
         #                <wfs:PropertyName>the_geom</wfs:PropertyName>
         #                <wfs:PropertyName>STATE</wfs:PropertyName>
@@ -1305,23 +1461,29 @@ class WFSQuery(IComplexDataInput):
         #                    <ogc:GmlObjectId gml:id="CONUS_States.508"/>
         #                </ogc:Filter>
         #            </wfs:Query>
-   
-        queryElement = etree.Element(nspath_eval('wfs:Query', namespaces), attrib = { "typeName":self.typeName })
+
+        queryElement = etree.Element(
+            nspath_eval('wfs:Query', namespaces), attrib={"typeName": self.typeName})
         for propertyName in self.propertyNames:
-            propertyNameElement = etree.SubElement(queryElement, nspath_eval('wfs:PropertyName', namespaces))
+            propertyNameElement = etree.SubElement(
+                queryElement, nspath_eval('wfs:PropertyName', namespaces))
             propertyNameElement.text = propertyName
-        if len(self.filters)>0:
-            filterElement = etree.SubElement(queryElement, nspath_eval('ogc:Filter', namespaces))
+        if len(self.filters) > 0:
+            filterElement = etree.SubElement(
+                queryElement, nspath_eval('ogc:Filter', namespaces))
             for filter in self.filters:
-                gmlObjectIdElement = etree.SubElement(filterElement, nspath_eval('ogc:GmlObjectId', namespaces), 
-                                                      attrib={nspath_eval('gml:id', namespaces):filter})
+                gmlObjectIdElement = etree.SubElement(
+                    filterElement, nspath_eval('ogc:GmlObjectId', namespaces),
+                                                      attrib={nspath_eval('gml:id', namespaces): filter})
         return queryElement
-        
+
+
 class GMLMultiPolygonFeatureCollection(FeatureCollection):
+
     '''
     Class that represents a FeatureCollection defined as a GML multi-polygon.
     '''
-    
+
     def __init__(self, polygons):
         '''
         Initializer accepts an array of polygons, where each polygon is an array of (lat,lon) tuples.
@@ -1329,7 +1491,7 @@ class GMLMultiPolygonFeatureCollection(FeatureCollection):
                               [(-92.8184, 39.5273), (-92.8184, 37.418), (-91.2363, 37.418), (-91.2363, 39.5273), (-92.8184, 39.5273)] ]
         '''
         self.polygons = polygons
-    
+
     def getXml(self):
         '''
             <wps:Data>
@@ -1362,58 +1524,73 @@ class GMLMultiPolygonFeatureCollection(FeatureCollection):
             </wps:Data>
         '''
         dataElement = etree.Element(nspath_eval('wps:Data', namespaces))
-        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"} )
+        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:
-            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))
+            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
-    
+
+
 def monitorExecution(execution, sleepSecs=3, download=False, filepath=None):
     '''
     Convenience method to monitor the status of a WPS execution till it completes (succesfully or not),
     and write the output to file after a succesfull job completion.
-    
+
     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
-    
+
     '''
-    
-    while execution.isComplete()==False:
+
+    while execution.isComplete() == False:
         execution.checkStatus(sleepSecs=sleepSecs)
         log.info('Execution status: %s' % execution.status)
-        
+
     if execution.isSucceded():
         if download:
             execution.getOutput(filepath=filepath)
         else:
-            for output in execution.processOutputs:               
+            for output in execution.processOutputs:
                 if output.reference is not None:
                     log.info('Output URL=%s' % output.reference)
     else:
         for ex in execution.errors:
-            log.error('Error: code=%s, locator=%s, text=%s' % (ex.code, ex.locator, ex.text))
+            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.
     '''
-    
+
     # ComplexData type
     if isinstance(value, ComplexData):
         return "mimeType=%s, encoding=%s, schema=%s" % (value.mimeType, value.encoding, value.schema)
@@ -1421,13 +1598,15 @@ def printValue(value):
     else:
         return value
 
+
 def printInputOutput(value, indent=''):
     '''
     Utility method to inspect an input/output element.
     '''
 
     # InputOutput fields
-    print('%s identifier=%s, title=%s, abstract=%s, data type=%s' % (indent, value.identifier, value.title, value.abstract, value.dataType))
+    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:
         print('%s Allowed Value: %s' % (indent, printValue(val)))
     if value.anyValue:
@@ -1438,10 +1617,12 @@ def printInputOutput(value, indent=''):
 
     # Input fields
     if isinstance(value, Input):
-        print('%s minOccurs=%d, maxOccurs=%d' % (indent, value.minOccurs, value.maxOccurs))
+        print('%s minOccurs=%d, maxOccurs=%d' %
+              (indent, value.minOccurs, value.maxOccurs))
 
     # Output fields
     if isinstance(value, Output):
-        print('%s reference=%s, mimeType=%s' % (indent, value.reference, value.mimeType))
+        print('%s reference=%s, mimeType=%s' %
+              (indent, value.reference, value.mimeType))
         for datum in value.data:
             print('%s Data Value: %s' % (indent, printValue(datum)))
diff --git a/tests/doctests/csw_geoserver.txt b/tests/doctests/csw_geoserver.txt
index 9b0aa2f..0a54e04 100644
--- a/tests/doctests/csw_geoserver.txt
+++ b/tests/doctests/csw_geoserver.txt
@@ -1,6 +1,6 @@
 >>> from __future__ import (absolute_import, division, print_function)
 >>> from owslib.csw import CatalogueServiceWeb
->>> c=CatalogueServiceWeb('http://sdi.georchestra.org/geoserver/ows')
+>>> c=CatalogueServiceWeb('http://sp7.irea.cnr.it/geoserver/ows')
 >>> c.getrecords2(typenames='csw:Record')
 >>> c.results.get('returned') > 0
 True
diff --git a/tests/doctests/csw_linz.txt b/tests/doctests/csw_linz.txt
index 4c7fc9e..4f54da4 100644
--- a/tests/doctests/csw_linz.txt
+++ b/tests/doctests/csw_linz.txt
@@ -14,11 +14,11 @@ Imports
     >>> c.provider.name
     'Land Information New Zealand'
     >>> prop = fes.PropertyIsLike('csw:AnyText', 'parcel boundaries')
-    >>> c.getrecords2([prop])
+    >>> c.getrecords2([prop], maxrecords=20)
     >>> c.results['matches']
     23
     >>> c.results['returned']
-    10
+    20
     >>> c.records['4d8c2d95-4ac2-1aa3-5b81-3cd4eff1ad0f'].title
     'NZ Strata Parcels'
     >>> c.records['4d8c2d95-4ac2-1aa3-5b81-3cd4eff1ad0f'].abstract
diff --git a/tests/doctests/csw_uuid_constrain.txt b/tests/doctests/csw_uuid_constrain.txt
index 5c938bd..1b1cdf2 100644
--- a/tests/doctests/csw_uuid_constrain.txt
+++ b/tests/doctests/csw_uuid_constrain.txt
@@ -23,7 +23,7 @@ Initialize CSW client
     
     # Test new function getrecords2()
     >>> uuid = fes.PropertyIsEqualTo(propertyname='sys.siteuuid', literal='{%s}' % AOOS)
-    >>> timeRange = fes.PropertyIsBetween(propertyname='apiso:modified', lower='2009-02-01', upper='2012-02-01')
+    >>> timeRange = fes.PropertyIsBetween(propertyname='apiso:modified', lower='2009-02-01', upper='2015-02-01')
     >>> word = fes.PropertyIsLike(propertyname='csw:AnyText', literal='*salinity*', escapeChar='\\', singleChar='?', wildCard='*')
     >>> box = fes.BBox(bbox)
 
@@ -34,7 +34,7 @@ Initialize CSW client
     ##########################################################
     >>> filter_list = [[uuid, word, timeRange], [bbox, word, timeRange]]
     >>> c.getrecords2(filter_list)
-    >>> c.results == {'matches': 1, 'nextrecord': 0, 'returned': 1}
+    >>> c.results == {'matches': 75, 'nextrecord': 11, 'returned': 10}
     True
 
     ##########################################################
diff --git a/tests/doctests/gm03_parse.txt b/tests/doctests/gm03_parse.txt
index dfb8dfa..17a19f8 100755
--- a/tests/doctests/gm03_parse.txt
+++ b/tests/doctests/gm03_parse.txt
@@ -19,9 +19,9 @@ Print testing some metadata elements
     >>> hasattr(gm03.data, 'comprehensive')
     True
     >>> len(gm03.data.comprehensive.elements)
-    10
+    13
     >>> sorted(list(gm03.data.comprehensive.elements.keys()))
-    ['address', 'contact', 'date', 'extent', 'extent_geographic_element', 'geographic_bounding_box', 'identification_point_of_contact', 'keywords', 'metadata', 'responsible_party']
+    ['address', 'citation', 'contact', 'data_identification', 'date', 'extent', 'extent_geographic_element', 'geographic_bounding_box', 'identification_point_of_contact', 'keywords', 'metadata', 'metadata_point_of_contact', 'responsible_party']
     >>> isinstance(gm03.data.comprehensive.date, list)
     True
     >>> len(gm03.data.comprehensive.date)
@@ -46,8 +46,7 @@ Test TID searching
     >>> search_tid2
     'xN8036063300808707346'
     >>> gm03.data.comprehensive.get_element_by_tid(search_tid2) is None
-    True
-
+    False
     >>> e = etree.parse(resource_file('gm03_example2.xml'))
     >>> gm03 = GM03(e)
     >>> gm03.data.comprehensive.geographic_bounding_box.extent_type_code
diff --git a/tests/doctests/iso_keywords.txt b/tests/doctests/iso_keywords.txt
new file mode 100755
index 0000000..66899ed
--- /dev/null
+++ b/tests/doctests/iso_keywords.txt
@@ -0,0 +1,26 @@
+
+Imports
+
+    >>> from __future__ import (absolute_import, division, print_function)
+    >>> from tests.utils import resource_file
+    >>> from owslib.etree import etree
+	>>> from owslib.iso import MD_Metadata
+
+Print testing some metadata elements
+
+    >>> e = etree.parse(resource_file('17bd184a-7e7d-4f81-95a5-041449a7212b_iso.xml'))
+    >>> md = MD_Metadata(e)
+    >>> md.identificationinfo[0].title
+    'Air temperature'
+
+    >>> len(md.identificationinfo[0].keywords2) == len(md.identificationinfo[0].keywords)
+    True
+
+    >>> len(md.identificationinfo[0].keywords2) > 0
+    True
+
+    >>> len(md.identificationinfo[0].keywords2[0].keywords) > 0
+    True
+
+    >>> md.identificationinfo[0].keywords2[0].thesaurus['title']
+    'GEMET - INSPIRE themes, version 1.0'
diff --git a/tests/doctests/tms.txt b/tests/doctests/tms.txt
index d440d13..3804e72 100644
--- a/tests/doctests/tms.txt
+++ b/tests/doctests/tms.txt
@@ -8,7 +8,7 @@ http://svn.osgeo.org/gdal/trunk/gdal/frmts/wms/WMSServerList.txt
 
 Find out what a TMS has to offer. Service metadata:
 
-    >>> service = tms.TileMapService('http://maps.opengeo.org/geowebcache/service/tms/1.0.0', timeout=120)
+    >>> service = tms.TileMapService('http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0', timeout=120)
     >>> service.identification.title
     'Tile Map Service'
     >>> service.identification.abstract
@@ -18,26 +18,26 @@ Find out what a TMS has to offer. Service metadata:
     >>> service.identification.version
     '1.0.0'
     >>> service.identification.url
-    'http://maps.opengeo.org/geowebcache'
+    'http://demo.opengeo.org/geoserver/gwc/'
     >>> len(service.contents) > 0
     True
-    >>> tm = service.contents['http://maps.opengeo.org/geowebcache/service/tms/1.0.0/bluemarble@EPSG%3A4326@png']
+    >>> tm = service.contents['http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/nasa%3Abluemarble@EPSG%3A4326@png']
     >>> tm.title
     'bluemarble'
 
 you can filter the contents by profile and srs:
     >>> len(service.items())
-    14
+    57
     >>> len(service.items('EPSG:900913'))
-    7
+    27
     >>> len(service.items(profile='global-mercator'))
-    7
+    27
     >>> len(service.items('EPSG:900913', profile='global-mercator'))
-    7
+    27
     >>> len(service.items('EPSG:4326', profile='global-mercator'))
     0
     >>> sorted(service.items('EPSG:4326'))[0]  # doctest: +ELLIPSIS
-    ('http://maps.opengeo.org/geowebcache/service/tms/1.0.0/blue@EPSG%3A4326@png', <owslib.tms.ContentMetadata object at ...>)
+    ('http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/maps%3Adark@EPSG%3A4326@jpeg', <owslib.tms.ContentMetadata object at ...>)
 
 The details of the TileMap are fetched on demand
     >>> tm._tile_map == None
@@ -67,21 +67,21 @@ The details of the TileMap are fetched on demand
 
 You can get tiles by their x,y,z indices, the title of the tilemap, projection and mime-type
     >>> service.gettile(10,10,0, title='bluemarble', srs='EPSG:4326', mimetype='image/png').geturl()    #doctest: +SKIP
-    'http://maps.opengeo.org/geowebcache/service/tms/1.0.0/bluemarble@EPSG%3A4326@png/0/10/10.png'
+    'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/bluemarble@EPSG%3A4326@png/0/10/10.png'
     >>> service.gettile(10,10,1, title='bluemarble', srs='EPSG:4326', mimetype='image/jpeg').geturl()   #doctest: +SKIP
-    'http://maps.opengeo.org/geowebcache/service/tms/1.0.0/bluemarble@EPSG%3A4326@jpeg/1/10/10.jpeg'
+    'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/bluemarble@EPSG%3A4326@jpeg/1/10/10.jpeg'
 
 if mimetype is ommited the tile is fetched from the first TileMap found:
     >>> service.gettile(10,10,1, title='bluemarble', srs='EPSG:900913').geturl()    #doctest: +SKIP
-    'http://maps.opengeo.org/geowebcache/service/tms/1.0.0/bluemarble@EPSG%3A900913@jpeg/1/10/10.jpeg'
+    'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/bluemarble@EPSG%3A900913@jpeg/1/10/10.jpeg'
 
 You can also specify the Tilemap by id:
-    >>> service.gettile(10,10,0, 'http://maps.opengeo.org/geowebcache/service/tms/1.0.0/bluemarble@EPSG%3A900913@jpeg').geturl()    #doctest: +SKIP
-    'http://maps.opengeo.org/geowebcache/service/tms/1.0.0/bluemarble@EPSG%3A900913@jpeg/0/10/10.jpeg'
+    >>> service.gettile(10,10,0, 'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/bluemarble@EPSG%3A900913@jpeg').geturl()    #doctest: +SKIP
+    'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0/bluemarble@EPSG%3A900913@jpeg/0/10/10.jpeg'
 
 An extensive test  with:
     >>> servers = [
-    ...     'http://maps.opengeo.org/geowebcache/service/tms/1.0.0',
+    ...     'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0',
     ...     'http://demo.opengeo.org/geoserver/gwc/service/tms/1.0.0',
     ...     'http://osm.omniscale.net/proxy/tms/1.0.0',
     ...     'http://apps.esdi-humboldt.cz/mapproxy/tms/1.0.0',
diff --git a/tests/doctests/wfs1_generic.txt b/tests/doctests/wfs1_generic.txt
index f81f481..8c50cca 100644
--- a/tests/doctests/wfs1_generic.txt
+++ b/tests/doctests/wfs1_generic.txt
@@ -114,3 +114,36 @@ WFS 1.1.0
     >>> feature = wfs.getfeature(typename=['sb:Project_Area'], maxfeatures=1, propertyname=None, outputFormat='application/json', srsname="urn:x-ogc:def:crs:EPSG:4326")
     >>> json.dumps(json.loads(feature.read()))
     '{...}'
+
+Test schema WFS 1.0.0
+
+    >>> wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288', version='1.0.0')
+    >>> schema = wfs.get_schema('footprint')
+    >>> len(schema['properties'])
+    4
+    >>> schema['properties']['summary']
+    'string'
+    >>> schema['geometry']
+    'Polygon'
+
+Test schema WFS 1.1.0
+
+    >>> wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288', version='1.1.0')
+    >>> schema = wfs.get_schema('footprint')
+    >>> len(schema['properties'])
+    4
+    >>> schema['properties']['summary']
+    'string'
+    >>> schema['geometry']
+    '3D Polygon'
+
+Test schema WFS 2.0.0
+
+    >>> wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288', version='2.0.0')
+    >>> schema = wfs.get_schema('footprint')
+    >>> len(schema['properties'])
+    4
+    >>> schema['properties']['summary']
+    'string'
+    >>> schema['geometry']
+    '3D Polygon'
diff --git a/tests/doctests/wms_GeoServerCapabilities.txt b/tests/doctests/wms_GeoServerCapabilities.txt
index ef7eb55..296a24c 100644
--- a/tests/doctests/wms_GeoServerCapabilities.txt
+++ b/tests/doctests/wms_GeoServerCapabilities.txt
@@ -69,7 +69,7 @@ Test nested layer
     'Parent Layer'
     
     >>> wms['parent_layer'].queryable
-    1
+    0
     
     >>> len(wms['parent_layer'].children)
     1
diff --git a/tests/doctests/wmts.txt b/tests/doctests/wmts.txt
index 7f768f5..2d238d5 100644
--- a/tests/doctests/wmts.txt
+++ b/tests/doctests/wmts.txt
@@ -36,8 +36,11 @@ Available Layers:
 
 Fetch a tile (using some defaults):
 
-  >>> tile = wmts.gettile(layer='MODIS_Terra_CorrectedReflectance_TrueColor', tilematrixset='EPSG4326_250m', tilematrix='0', row=0, column=0, format="image/jpeg")
-  >>> out = open(scratch_file('nasa_modis_terra_truecolour.jpg'), 'wb')
-  >>> bytes_written = out.write(tile.read())
-  >>> out.close()
+    >>> tile = wmts.gettile(layer='MODIS_Terra_CorrectedReflectance_TrueColor', tilematrixset='EPSG4326_250m', tilematrix='0', row=0, column=0, format="image/jpeg")
+    >>> out = open(scratch_file('nasa_modis_terra_truecolour.jpg'), 'wb')
+    >>> bytes_written = out.write(tile.read())
+    >>> out.close()
 
+Test a WMTS without a ServiceProvider tag in Capababilities XML
+
+    >>> wmts = WebMapTileService('http://data.geus.dk/arcgis/rest/services/OneGeologyGlobal/S071_G2500_OneGeology/MapServer/WMTS/1.0.0/WMTSCapabilities.xml')
diff --git a/tests/doctests/wps_describeprocess_bbox.txt b/tests/doctests/wps_describeprocess_bbox.txt
new file mode 100644
index 0000000..0f20e82
--- /dev/null
+++ b/tests/doctests/wps_describeprocess_bbox.txt
@@ -0,0 +1,51 @@
+Python doctest file to simulate a WPS DescribeProcess invocation.
+This test is testing the BoundingBox process of the Emu service (https://github.com/bird-house/emu).
+This test does not execute any live HTTP request, rather it parses XML files containing pre-made HTTP responses.
+
+Imports
+
+    >>> from __future__ import (absolute_import, division, print_function)
+    >>> from tests.utils import resource_file
+    >>> from owslib.wps import WebProcessingService, printInputOutput
+
+Initialize WPS client
+
+    >>> wps = WebProcessingService('http://localhost:8094/wps',skip_caps=True)
+
+Execute fake invocation of DescribeProcess operation by parsing cached response from Emu service
+
+    >>> xml = open(resource_file('wps_bbox_DescribeProcess.xml'), 'rb').read()
+    >>> process = wps.describeprocess('bbox', xml=xml)
+
+Check process description
+
+    >>> process.identifier
+    'bbox'
+    >>> process.title
+    'Bounding Box'
+
+Check process inputs
+
+    >>> for input in process.dataInputs:
+    ...     print('Process input:')
+    ...     printInputOutput(input)
+    ... 
+    Process input:
+     identifier=bbox, title=Bounding Box, abstract=None, data type=BoundingBoxData
+     Supported Value: EPSG:4326
+     Supported Value: EPSG:3035
+     Default Value: EPSG:4326 
+     minOccurs=1, maxOccurs=1
+
+
+Check process outputs
+
+    >>> for output in process.processOutputs:
+    ...     print('Process output:')
+    ...     printInputOutput(output)
+    ... 
+    Process output:
+     identifier=bbox, title=Bounding Box, abstract=None, data type=BoundingBoxData
+     Supported Value: EPSG:4326
+     Default Value: EPSG:4326 
+     reference=None, mimeType=None
diff --git a/tests/resources/17bd184a-7e7d-4f81-95a5-041449a7212b_iso.xml b/tests/resources/17bd184a-7e7d-4f81-95a5-041449a7212b_iso.xml
new file mode 100644
index 0000000..c51ac0a
--- /dev/null
+++ b/tests/resources/17bd184a-7e7d-4f81-95a5-041449a7212b_iso.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?><gmd:MD_Metadata xsi:schemaLocation="http://www.isotc211.org/2005/gmd http://schemas.opengis.net/iso/19139/20060504/gmd/gmd.xsd" xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">
+<gmd:fileIdentifier>
+<gco:CharacterString>17bd184a-7e7d-4f81-95a5-041449a7212b</gco:CharacterString>
+</gmd:fileIdentifier>
+<gmd:language>
+<gmd:LanguageCode codeList="http://www.loc.gov/standards/iso639-2/" codeListValue="eng">eng</gmd:LanguageCode>
+</gmd:language>
+<gmd:characterSet>
+<gmd:MD_CharacterSetCode codeSpace="ISOTC211/19115" codeListValue="MD_CharacterSetCode_utf8" codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_CharacterSetCode">MD_CharacterSetCode_utf8</gmd:MD_CharacterSetCode>
+</gmd:characterSet>
+<gmd:hierarchyLevel>
+<gmd:MD_ScopeCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#MD_ScopeCode" codeListValue="dataset">dataset</gmd:MD_ScopeCode>
+</gmd:hierarchyLevel>
+<gmd:contact>
+<gmd:CI_ResponsibleParty>
+<gmd:organisationName>
+<gco:CharacterString>Instituto Português do Mar e da Atmosfera</gco:CharacterString>
+</gmd:organisationName>
+<gmd:contactInfo>
+<gmd:CI_Contact>
+<gmd:address>
+<gmd:CI_Address>
+<gmd:electronicMailAddress>
+<gco:CharacterString>email at ipma.pt</gco:CharacterString>
+</gmd:electronicMailAddress>
+</gmd:CI_Address>
+</gmd:address>
+</gmd:CI_Contact>
+</gmd:contactInfo>
+<gmd:role>
+<gmd:CI_RoleCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#CI_RoleCode" codeListValue="pointOfContact">pointOfContact</gmd:CI_RoleCode>
+</gmd:role>
+</gmd:CI_ResponsibleParty>
+</gmd:contact>
+<gmd:dateStamp>
+<gco:Date>2015-12-16</gco:Date>
+</gmd:dateStamp>
+<gmd:metadataStandardName>
+<gco:CharacterString>ISO19115</gco:CharacterString>
+</gmd:metadataStandardName>
+<gmd:metadataStandardVersion>
+<gco:CharacterString>2003/Cor.1:2006</gco:CharacterString>
+</gmd:metadataStandardVersion>
+<gmd:identificationInfo>
+<gmd:MD_DataIdentification>
+<gmd:citation>
+<gmd:CI_Citation>
+<gmd:title>
+<gco:CharacterString>Air temperature</gco:CharacterString>
+</gmd:title>
+<gmd:date>
+<gmd:CI_Date>
+<gmd:date>
+<gco:Date>2015-12-16</gco:Date>
+</gmd:date>
+<gmd:dateType>
+<gmd:CI_DateTypeCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#CI_DateTypeCode" codeListValue="creation">creation</gmd:CI_DateTypeCode>
+</gmd:dateType>
+</gmd:CI_Date>
+</gmd:date>
+<gmd:identifier>
+<gmd:RS_Identifier>
+<gmd:code>
+<gco:CharacterString>17bd184a-7e7d-4f81-95a5-041449a7212b</gco:CharacterString>
+</gmd:code>
+</gmd:RS_Identifier>
+</gmd:identifier>
+</gmd:CI_Citation>
+</gmd:citation>
+<gmd:abstract>
+<gco:CharacterString>Air temperature for a 10 year period</gco:CharacterString>
+</gmd:abstract>
+<gmd:pointOfContact>
+<gmd:CI_ResponsibleParty>
+<gmd:organisationName>
+<gco:CharacterString>IPMA</gco:CharacterString>
+</gmd:organisationName>
+<gmd:contactInfo>
+<gmd:CI_Contact>
+<gmd:address>
+<gmd:CI_Address>
+<gmd:electronicMailAddress>
+<gco:CharacterString>email at ipma.pt</gco:CharacterString>
+</gmd:electronicMailAddress>
+</gmd:CI_Address>
+</gmd:address>
+</gmd:CI_Contact>
+</gmd:contactInfo>
+<gmd:role>
+<gmd:CI_RoleCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#CI_RoleCode" codeListValue="originator">originator</gmd:CI_RoleCode>
+</gmd:role>
+</gmd:CI_ResponsibleParty>
+</gmd:pointOfContact>
+<gmd:descriptiveKeywords>
+<gmd:MD_Keywords>
+<gmd:keyword>
+<gco:CharacterString>Atmospheric conditions</gco:CharacterString>
+</gmd:keyword>
+<gmd:thesaurusName>
+<gmd:CI_Citation>
+<gmd:title>
+<gco:CharacterString>GEMET - INSPIRE themes, version 1.0</gco:CharacterString>
+</gmd:title>
+<gmd:date>
+<gmd:CI_Date>
+<gmd:date>
+<gco:Date>2008-06-01</gco:Date>
+</gmd:date>
+<gmd:dateType>
+<gmd:CI_DateTypeCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#CI_DateTypeCode" codeListValue="publication">publication</gmd:CI_DateTypeCode>
+</gmd:dateType>
+</gmd:CI_Date>
+</gmd:date>
+</gmd:CI_Citation>
+</gmd:thesaurusName>
+</gmd:MD_Keywords>
+</gmd:descriptiveKeywords>
+<gmd:descriptiveKeywords>
+<gmd:MD_Keywords>
+<gmd:keyword>
+<gco:CharacterString>Temperature</gco:CharacterString>
+</gmd:keyword>
+</gmd:MD_Keywords>
+</gmd:descriptiveKeywords>
+<gmd:resourceConstraints>
+<gmd:MD_Constraints>
+<gmd:useLimitation>
+<gco:CharacterString>Conditions unknown</gco:CharacterString>
+</gmd:useLimitation>
+</gmd:MD_Constraints>
+</gmd:resourceConstraints>
+<gmd:resourceConstraints>
+<gmd:MD_LegalConstraints>
+<gmd:accessConstraints>
+<gmd:MD_RestrictionCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#MD_RestrictionCode" codeListValue="otherRestrictions">otherRestrictions</gmd:MD_RestrictionCode>
+</gmd:accessConstraints>
+<gmd:otherConstraints>
+<gco:CharacterString>no limitation</gco:CharacterString>
+</gmd:otherConstraints>
+</gmd:MD_LegalConstraints>
+</gmd:resourceConstraints>
+<gmd:language>
+<gmd:LanguageCode codeList="http://www.loc.gov/standards/iso639-2/" codeListValue="por">por</gmd:LanguageCode>
+</gmd:language>
+<gmd:topicCategory>
+<gmd:MD_TopicCategoryCode>climatologyMeteorologyAtmosphere</gmd:MD_TopicCategoryCode>
+</gmd:topicCategory>
+<gmd:extent>
+<gmd:EX_Extent>
+<gmd:geographicElement>
+<gmd:EX_GeographicBoundingBox>
+<gmd:westBoundLongitude>
+<gco:Decimal>-9.50</gco:Decimal>
+</gmd:westBoundLongitude>
+<gmd:eastBoundLongitude>
+<gco:Decimal>-6.19</gco:Decimal>
+</gmd:eastBoundLongitude>
+<gmd:southBoundLatitude>
+<gco:Decimal>36.96</gco:Decimal>
+</gmd:southBoundLatitude>
+<gmd:northBoundLatitude>
+<gco:Decimal>42.15</gco:Decimal>
+</gmd:northBoundLatitude>
+</gmd:EX_GeographicBoundingBox>
+</gmd:geographicElement>
+</gmd:EX_Extent>
+</gmd:extent>
+</gmd:MD_DataIdentification>
+</gmd:identificationInfo>
+<gmd:distributionInfo>
+<gmd:MD_Distribution>
+<gmd:distributionFormat>
+<gmd:MD_Format>
+<gmd:name>
+<gco:CharacterString>unknown</gco:CharacterString>
+</gmd:name>
+<gmd:version>
+<gco:CharacterString>unknown</gco:CharacterString>
+</gmd:version>
+</gmd:MD_Format>
+</gmd:distributionFormat>
+<gmd:transferOptions>
+<gmd:MD_DigitalTransferOptions>
+<gmd:onLine>
+<gmd:CI_OnlineResource>
+<gmd:linkage>
+<gmd:URL>http://ipma.pt</gmd:URL>
+</gmd:linkage>
+</gmd:CI_OnlineResource>
+</gmd:onLine>
+</gmd:MD_DigitalTransferOptions>
+</gmd:transferOptions>
+</gmd:MD_Distribution>
+</gmd:distributionInfo>
+<gmd:dataQualityInfo>
+<gmd:DQ_DataQuality>
+<gmd:scope>
+<gmd:DQ_Scope>
+<gmd:level>
+<gmd:MD_ScopeCode codeListValue="dataset" codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#MD_ScopeCode">dataset</gmd:MD_ScopeCode>
+</gmd:level>
+</gmd:DQ_Scope>
+</gmd:scope>
+<gmd:report>
+<gmd:DQ_DomainConsistency xsi:type="gmd:DQ_DomainConsistency_Type">
+<gmd:result>
+<gmd:DQ_ConformanceResult xsi:type="gmd:DQ_ConformanceResult_Type">
+<gmd:specification>
+<gmd:CI_Citation>
+<gmd:title>
+<gco:CharacterString>COMMISSION REGULATION (EC) No 1205/2008 of 3 December 2008 implementing Directive 2007/2/EC of the European Parliament and of the Council as regards metadata</gco:CharacterString>
+</gmd:title>
+<gmd:date>
+<gmd:CI_Date>
+<gmd:date>
+<gco:Date>2008-12-04</gco:Date>
+</gmd:date>
+<gmd:dateType>
+<gmd:CI_DateTypeCode codeList="http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/resources/Codelist/ML_gmxCodelists.xml#CI_DateTypeCode" codeListValue="publication">publication</gmd:CI_DateTypeCode>
+</gmd:dateType>
+</gmd:CI_Date>
+</gmd:date>
+</gmd:CI_Citation>
+</gmd:specification>
+<gmd:explanation>
+<gco:CharacterString>See the referenced specification</gco:CharacterString>
+</gmd:explanation>
+<gmd:pass gco:nilReason="template"/>
+</gmd:DQ_ConformanceResult>
+</gmd:result>
+</gmd:DQ_DomainConsistency>
+</gmd:report>
+</gmd:DQ_DataQuality>
+</gmd:dataQualityInfo>
+</gmd:MD_Metadata>
diff --git a/tests/resources/wps_bbox_DescribeProcess.xml b/tests/resources/wps_bbox_DescribeProcess.xml
new file mode 100644
index 0000000..fdcd202
--- /dev/null
+++ b/tests/resources/wps_bbox_DescribeProcess.xml
@@ -0,0 +1,39 @@
+<?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.openg
+is.net/wps/1.0.0/wpsDescribeProcess_response.xsd" service="WPS" version="1.0.0" xml:lang="en-CA">
+    <ProcessDescription wps:processVersion="0.1" storeSupported="true" statusSupported="true">
+        <ows:Identifier>bbox</ows:Identifier>
+        <ows:Title>Bounding Box</ows:Title>
+        <ows:Abstract>Testing BoundingBox Input/Output Parameter</ows:Abstract>
+        <ows:Metadata xlink:title="home" xlink:href="http://emu.readthedocs.org/en/latest/index.html"/>
+        <DataInputs>
+            <Input minOccurs="1" maxOccurs="1">
+                <ows:Identifier>bbox</ows:Identifier>
+                <ows:Title>Bounding Box</ows:Title>
+                <BoundingBoxData>
+                    <Default>
+                        <CRS>EPSG:4326</CRS>
+                    </Default>
+                    <Supported>
+                        <CRS>EPSG:4326</CRS>
+                        <CRS>EPSG:3035</CRS>
+                    </Supported>
+                </BoundingBoxData>
+            </Input>
+        </DataInputs>
+        <ProcessOutputs>
+            <Output>
+                <ows:Identifier>bbox</ows:Identifier>
+                <ows:Title>Bounding Box</ows:Title>
+                <BoundingBoxOutput>
+                    <Default>
+                        <CRS>EPSG:4326</CRS>
+                    </Default>
+                    <Supported>
+                        <CRS>EPSG:4326</CRS>
+                    </Supported>
+                </BoundingBoxOutput>
+            </Output>
+        </ProcessOutputs>
+    </ProcessDescription>
+</wps:ProcessDescriptions>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/owslib.git



More information about the Pkg-grass-devel mailing list