[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