This is an automated email from the git hooks/post-receive script. johanvdw-guest pushed a commit to branch master in repository owslib.
commit 8519ddcc26c6072d5d10de5f1c6d1022381a60c8 Author: Johan Van de Wauw <jo...@vandewauw.be> Date: Thu Sep 24 22:26:42 2015 +0200 Imported Upstream version 0.9.2 --- VERSION.txt | 2 +- examples/wms-getfeatureinfo.py | 33 +++++++ owslib/__init__.py | 2 +- owslib/util.py | 19 +++- owslib/wms.py | 141 +++++++++++++++++++++------ owslib/wmts.py | 71 +++++++++++++- tests/doctests/wms_GeoServerCapabilities.txt | 20 ++++ tests/doctests/wms_getfeatureinfo.txt | 24 +++++ tests/doctests/wmts_RESTonly.txt | 34 +++++++ tests/doctests/wmts_geoserver21.txt | 2 + tests/resources/wms_geoserver-cap.xml | 24 +++-- 11 files changed, 325 insertions(+), 47 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index f374f66..2003b63 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.9.1 +0.9.2 diff --git a/examples/wms-getfeatureinfo.py b/examples/wms-getfeatureinfo.py new file mode 100644 index 0000000..51ed5e0 --- /dev/null +++ b/examples/wms-getfeatureinfo.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +__author__ = 'Juergen Weichand' + +from owslib.wms import WebMapService +wms = WebMapService('http://geoserv.weichand.de:8080/geoserver/wms') + +# GetMap (image/jpeg) +response = wms.getmap( + layers=['bvv:gmd_ex'], + srs='EPSG:31468', + bbox=(4500000,5500000,4505000,5505000), + size=(500,500), + format='image/jpeg') + +out = open('/tmp/getmap-response.jpeg', 'wb') +out.write(response.read()) +out.close() + +# GetFeatureInfo (text/html) +response = wms.getfeatureinfo( + layers=['bvv:gmd_ex'], + srs='EPSG:31468', + bbox=(4500000,5500000,4505000,5505000), + size=(500,500), + format='image/jpeg', + query_layers=['bvv:gmd_ex'], + info_format="text/html", + xy=(250,250)) + +out = open('/tmp/getfeatureinfo-response.html', 'wb') +out.write(response.read()) +out.close() \ No newline at end of file diff --git a/owslib/__init__.py b/owslib/__init__.py index e063736..c57c469 100644 --- a/owslib/__init__.py +++ b/owslib/__init__.py @@ -1,3 +1,3 @@ from __future__ import (absolute_import, division, print_function) -__version__ = '0.9.1' +__version__ = '0.9.2' diff --git a/owslib/util.py b/owslib/util.py index 1be88a4..22bcaa7 100644 --- a/owslib/util.py +++ b/owslib/util.py @@ -147,7 +147,7 @@ def openURL(url_base, data=None, method='Get', cookies=None, username=None, pass Uses requests library but with additional checks for OGC service exceptions and url formatting. Also handles cookies and simple user password authentication. """ - headers = {} + headers = None rkwargs = {} rkwargs['timeout'] = timeout @@ -165,7 +165,7 @@ def openURL(url_base, data=None, method='Get', cookies=None, username=None, pass if method.lower() == 'post': try: xml = etree.fromstring(data) - headers['Content-Type'] = "text/xml" + headers = {'Content-Type': 'text/xml'} except (ParseError, UnicodeEncodeError): pass @@ -173,6 +173,7 @@ def openURL(url_base, data=None, method='Get', cookies=None, username=None, pass elif method.lower() == 'get': rkwargs['params'] = data + else: raise ValueError("Unknown method ('%s'), expected 'get' or 'post'" % method) @@ -181,6 +182,7 @@ def openURL(url_base, data=None, method='Get', cookies=None, username=None, pass req = requests.request(method.upper(), url_base, + headers=headers, **rkwargs) if req.status_code in [400, 401]: @@ -581,3 +583,16 @@ except: # 2.6 from ordereddict import OrderedDict +def which_etree(): + """decipher which etree library is being used by OWSLib""" + + which_etree = None + + if 'lxml' in etree.__file__: + which_etree = 'lxml.etree' + elif 'xml/etree' in etree.__file__: + which_etree = 'xml.etree' + elif 'elementree' in etree.__file__: + which_etree = 'elementtree.ElementTree' + + return which_etree diff --git a/owslib/wms.py b/owslib/wms.py index 88cd147..372a112 100644 --- a/owslib/wms.py +++ b/owslib/wms.py @@ -123,13 +123,16 @@ class WebMapService(object): #recursively gather content metadata for all layer elements. #To the WebMapService.contents store only metadata of named layers. def gather_layers(parent_elem, parent_metadata): + layers = [] for index, elem in enumerate(parent_elem.findall('Layer')): cm = ContentMetadata(elem, parent=parent_metadata, index=index+1, parse_remote_metadata=parse_remote_metadata) if cm.id: if cm.id in self.contents: warnings.warn('Content metadata for layer "%s" already exists. Using child layer' % cm.id) + layers.append(cm) self.contents[cm.id] = cm - gather_layers(elem, cm) + cm.children = gather_layers(elem, cm) + return layers gather_layers(caps, None) #exceptions @@ -160,6 +163,41 @@ class WebMapService(object): raise ServiceException(err_message, se_xml) return u + def __build_getmap_request(self, layers=None, styles=None, srs=None, bbox=None, + format=None, size=None, time=None, transparent=False, + bgcolor=None, exceptions=None, **kwargs): + + request = {'version': self.version, 'request': 'GetMap'} + + # check layers and styles + assert len(layers) > 0 + request['layers'] = ','.join(layers) + if styles: + assert len(styles) == len(layers) + request['styles'] = ','.join(styles) + else: + request['styles'] = '' + + # size + request['width'] = str(size[0]) + request['height'] = str(size[1]) + + request['srs'] = str(srs) + request['bbox'] = ','.join([repr(x) for x in bbox]) + request['format'] = str(format) + request['transparent'] = str(transparent).upper() + request['bgcolor'] = '0x' + bgcolor[1:7] + request['exceptions'] = str(exceptions) + + if time is not None: + request['time'] = str(time) + + if kwargs: + for kw in kwargs: + request[kw]=kwargs[kw] + + return request + def getmap(self, layers=None, styles=None, srs=None, bbox=None, format=None, size=None, time=None, transparent=False, bgcolor='#FFFFFF', @@ -213,37 +251,59 @@ class WebMapService(object): base_url = next((m.get('url') for m in self.getOperationByName('GetMap').methods if m.get('type').lower() == method.lower())) except StopIteration: base_url = self.url - request = {'version': self.version, 'request': 'GetMap'} - - # check layers and styles - assert len(layers) > 0 - request['layers'] = ','.join(layers) - if styles: - assert len(styles) == len(layers) - request['styles'] = ','.join(styles) - else: - request['styles'] = '' - # size - request['width'] = str(size[0]) - request['height'] = str(size[1]) + request = self.__build_getmap_request(layers=layers, styles=styles, srs=srs, bbox=bbox, + format=format, size=size, time=time, transparent=transparent, + bgcolor=bgcolor, exceptions=exceptions, kwargs=kwargs) - request['srs'] = str(srs) - request['bbox'] = ','.join([repr(x) for x in bbox]) - request['format'] = str(format) - request['transparent'] = str(transparent).upper() - request['bgcolor'] = '0x' + bgcolor[1:7] - request['exceptions'] = str(exceptions) - - if time is not None: - request['time'] = str(time) + data = urlencode(request) - if kwargs: - for kw in kwargs: - request[kw]=kwargs[kw] + u = openURL(base_url, data, method, username=self.username, password=self.password, timeout=timeout or self.timeout) + + # check for service exceptions, and return + if u.info()['Content-Type'] == 'application/vnd.ogc.se_xml': + se_xml = u.read() + se_tree = etree.fromstring(se_xml) + err_message = six.text_type(se_tree.find('ServiceException').text).strip() + raise ServiceException(err_message, se_xml) + return u + + + def getfeatureinfo(self, layers=None, styles=None, srs=None, bbox=None, + format=None, size=None, time=None, transparent=False, + bgcolor='#FFFFFF', + exceptions='application/vnd.ogc.se_xml', + query_layers = None, xy=None, info_format=None, feature_count=20, + method='Get', + timeout=None, + **kwargs + ): + try: + base_url = next((m.get('url') for m in self.getOperationByName('GetFeatureInfo').methods if m.get('type').lower() == method.lower())) + except StopIteration: + base_url = self.url + + # GetMap-Request + request = self.__build_getmap_request(layers=layers, styles=styles, srs=srs, bbox=bbox, + format=format, size=size, time=time, transparent=transparent, + bgcolor=bgcolor, exceptions=exceptions, kwargs=kwargs) + + # extend to GetFeatureInfo-Request + request['request'] = 'GetFeatureInfo' + + if not query_layers: + __str_query_layers = ','.join(layers) + else: + __str_query_layers = ','.join(query_layers) + + request['query_layers'] = __str_query_layers + request['x'] = str(xy[0]) + request['y'] = str(xy[1]) + request['info_format'] = info_format + request['feature_count'] = str(feature_count) data = urlencode(request) - + u = openURL(base_url, data, method, username=self.username, password=self.password, timeout=timeout or self.timeout) # check for service exceptions, and return @@ -253,16 +313,13 @@ class WebMapService(object): err_message = six.text_type(se_tree.find('ServiceException').text).strip() raise ServiceException(err_message, se_xml) return u - + def getServiceXML(self): xml = None if self._capabilities is not None: xml = etree.tostring(self._capabilities) return xml - def getfeatureinfo(self): - raise NotImplementedError - def getOperationByName(self, name): """Return a named content item.""" for item in self.operations: @@ -322,7 +379,7 @@ class ContentMetadata: Implements IContentMetadata. """ - def __init__(self, elem, parent=None, index=0, parse_remote_metadata=False, timeout=30): + def __init__(self, elem, parent=None, children=None, index=0, parse_remote_metadata=False, timeout=30): if elem.tag != 'Layer': raise ValueError('%s should be a Layer' % (elem,)) @@ -331,6 +388,8 @@ class ContentMetadata: self.index = "%s.%d" % (parent.index, index) else: self.index = str(index) + + self._children = children self.id = self.name = testXMLValue(elem.find('Name')) @@ -510,6 +569,24 @@ class ContentMetadata: for child in elem.findall('Layer'): self.layers.append(ContentMetadata(child, self)) + @property + def children(self): + return self._children + + @children.setter + def children(self, value): + if self._children is None: + 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 f915f6f..ec522f4 100644 --- a/owslib/wmts.py +++ b/owslib/wmts.py @@ -31,6 +31,7 @@ would be appreciated. from __future__ import (absolute_import, division, print_function) +from random import randint import warnings import six from six.moves import filter @@ -199,8 +200,11 @@ class WebMapTileService(object): # serviceOperations metadata self.operations = [] - for elem in self._capabilities.find(_OPERATIONS_METADATA_TAG)[:]: - self.operations.append(OperationsMetadata(elem)) + serviceop = self._capabilities.find(_OPERATIONS_METADATA_TAG) + # REST only WMTS does not have any Operations + if serviceop is not None: + for elem in serviceop[:]: + self.operations.append(OperationsMetadata(elem)) # serviceContents metadata: our assumption is that services use # a top-level layer as a metadata organizer, nothing more. @@ -293,7 +297,6 @@ LAYER=VIIRS_CityLights_2012&STYLE=default&TILEMATRIXSET=EPSG4326_500m&\ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' """ - request = {'version': self.version, 'request': 'GetTile'} if (layer is None): raise ValueError("layer is mandatory (cannot be None)") @@ -329,6 +332,58 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' data = urlencode(request, True) return data + def buildTileResource(self, layer=None, style=None, format=None, + tilematrixset=None, tilematrix=None, row=None, + column=None, **kwargs): + + tileresourceurls = [] + for resourceURL in self[layer].resourceURLs: + if resourceURL['resourceType'] == 'tile': + tileresourceurls.append(resourceURL) + numres = len(tileresourceurls) + if numres > 0: + # choose random ResourceURL if more than one available + resindex = randint(0, numres - 1) + resurl = tileresourceurls[resindex]['template'] + if tilematrixset: + resurl = resurl.replace('{TileMatrixSet}', tilematrixset) + resurl = resurl.replace('{TileMatrix}', tilematrix) + resurl = resurl.replace('{TileRow}', row) + resurl = resurl.replace('{TileCol}', column) + if style: + resurl = resurl.replace('{Style}', style) + return resurl + + return None + + @property + def restonly(self): + + # if OperationsMetadata is missing completely --> use REST + if len(self.operations) == 0: + return True + + # check if KVP or RESTful are available + restenc = False + kvpenc = False + for operation in self.operations: + if operation.name == 'GetTile': + for method in operation.methods: + if 'kvp' in str(method['constraints']).lower(): + kvpenc = True + if 'rest' in str(method['constraints']).lower(): + restenc = True + + # if KVP is available --> use KVP + if kvpenc: + return False + + # if the operation has no constraint --> use KVP + if not kvpenc and not restenc: + return False + + return restenc + def gettile(self, base_url=None, layer=None, style=None, format=None, tilematrixset=None, tilematrix=None, row=None, column=None, **kwargs): @@ -379,6 +434,16 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' """ vendor_kwargs = self.vendor_kwargs or {} vendor_kwargs.update(kwargs) + + # REST only WMTS + if self.restonly: + resurl = self.buildTileResource( + layer, style, format, tilematrixset, tilematrix, + row, column, **vendor_kwargs) + u = openURL(resurl, username=self.username, password=self.password) + return u + + # KVP implemetation data = self.buildTileRequest(layer, style, format, tilematrixset, tilematrix, row, column, **vendor_kwargs) diff --git a/tests/doctests/wms_GeoServerCapabilities.txt b/tests/doctests/wms_GeoServerCapabilities.txt index c8f31ef..ef7eb55 100644 --- a/tests/doctests/wms_GeoServerCapabilities.txt +++ b/tests/doctests/wms_GeoServerCapabilities.txt @@ -62,7 +62,27 @@ Test single item accessor >>> x = wms['opengeo:poi'].styles >>> x == {'point': {'legend': 'http://localhost:8080/geoserver/wms?request=GetLegendGraphic&format=image%2Fpng&width=20&height=20&layer=poi', 'title': 'A boring default style'}} True + +Test nested layer + + >>> wms['parent_layer'].title + 'Parent Layer' + + >>> wms['parent_layer'].queryable + 1 + + >>> len(wms['parent_layer'].children) + 1 + >>> wms['parent_layer'].children[0].title + 'Child Layer' + + >>> len(wms['child_layer'].children) + 0 + + >>> wms['child_layer'].parent.title + 'Parent Layer' + Expect a KeyError for invalid names >>> wms['utterly bogus'].title diff --git a/tests/doctests/wms_getfeatureinfo.txt b/tests/doctests/wms_getfeatureinfo.txt new file mode 100644 index 0000000..a3be31a --- /dev/null +++ b/tests/doctests/wms_getfeatureinfo.txt @@ -0,0 +1,24 @@ +>>> from owslib.wms import WebMapService +>>> wms = WebMapService('http://geoserv.weichand.de:8080/geoserver/wms') + +>>> res1 = wms.getfeatureinfo(layers=['bvv:lkr_ex'], srs='EPSG:31468', bbox=(4500000,5500000,4500500,5500500), size=(500,500), format='image/jpeg', info_format="text/html", xy=(250,250)) +>>> html_string1 = res1.read().decode("utf-8") +>>> ('lkr_ex' in html_string1) +True +>>> ('gmd_ex' in html_string1) +False + +>>> res2 = wms.getfeatureinfo(layers=['bvv:lkr_ex','bvv:gmd_ex'], srs='EPSG:31468', bbox=(4500000,5500000,4500500,5500500), size=(500,500), format='image/jpeg', info_format="text/html", xy=(250,250)) +>>> html_string2 = res2.read().decode("utf-8") +>>> ('lkr_ex' in html_string2) +True +>>> ('gmd_ex' in html_string2) +True + +>>> res3 = wms.getfeatureinfo(layers=['bvv:lkr_ex','bvv:gmd_ex'], srs='EPSG:31468', bbox=(4500000,5500000,4500500,5500500), size=(500,500), format='image/jpeg', query_layers=['bvv:lkr_ex'], info_format="text/html", xy=(250,250)) +>>> html_string3 = res3.read().decode("utf-8") +>>> ('lkr_ex' in html_string3) +True +>>> ('gmd_ex' in html_string3) +False + diff --git a/tests/doctests/wmts_RESTonly.txt b/tests/doctests/wmts_RESTonly.txt new file mode 100644 index 0000000..4d6de9d --- /dev/null +++ b/tests/doctests/wmts_RESTonly.txt @@ -0,0 +1,34 @@ +Imports: + + >>> from __future__ import (absolute_import, division, print_function) + >>> from tests.utils import scratch_file + +ServiceMetadata: + + >>> from owslib.wmts import WebMapTileService + >>> wmts = WebMapTileService("http://geoserv.weichand.de/mapproxy/wmts/1.0.0/WMTSCapabilities.xml") + >>> wmts.identification.type + 'OGC WMTS' + >>> wmts.identification.version + '1.0.0' + >>> wmts.identification.title + 'WMTS-Testserver DOP80' + +Content: + + >>> sorted(list(wmts.contents)) + ['dop80'] + +RESTful WMTS: + + >>> wmts.restonly + True + + >>> wmts.buildTileResource(layer='dop80', tilematrixset='webmercator', tilematrix='11', row='706', column='1089') + 'http://geoserv.weichand.de/mapproxy/wmts/dop80/webmercator/11/1089/706.png' + + >>> tile = wmts.gettile(layer='dop80', tilematrixset='webmercator', tilematrix='11', row='706', column='1089') + >>> out = open(scratch_file('bvv_bayern_dop80.png'), 'wb') + >>> bytes_written = out.write(tile.read()) + >>> out.close() + diff --git a/tests/doctests/wmts_geoserver21.txt b/tests/doctests/wmts_geoserver21.txt index 378d5d6..63f3396 100644 --- a/tests/doctests/wmts_geoserver21.txt +++ b/tests/doctests/wmts_geoserver21.txt @@ -27,6 +27,8 @@ Test capabilities >>> wmts.identification.fees + >>> wmts.restonly + False Service Provider: diff --git a/tests/resources/wms_geoserver-cap.xml b/tests/resources/wms_geoserver-cap.xml index 69eecae..729d103 100644 --- a/tests/resources/wms_geoserver-cap.xml +++ b/tests/resources/wms_geoserver-cap.xml @@ -140,14 +140,14 @@ <KeywordList/> <SRS>EPSG:4326</SRS> <!--WKT definition of this CRS: -GEOGCS["WGS 84", - DATUM["World Geodetic System 1984", - SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], - AUTHORITY["EPSG","6326"]], - PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], - UNIT["degree", 0.017453292519943295], - AXIS["Geodetic longitude", EAST], - AXIS["Geodetic latitude", NORTH], +GEOGCS["WGS 84", + DATUM["World Geodetic System 1984", + SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], + AUTHORITY["EPSG","6326"]], + PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], + UNIT["degree", 0.017453292519943295], + AXIS["Geodetic longitude", EAST], + AXIS["Geodetic latitude", NORTH], AUTHORITY["EPSG","4326"]]--> <LatLonBoundingBox minx="-74.012" miny="40.708" maxx="-74.002" maxy="40.72"/> <BoundingBox SRS="EPSG:4326" minx="-74.012" miny="40.708" maxx="-74.002" maxy="40.72"/> @@ -169,6 +169,14 @@ GEOGCS["WGS 84", </LegendURL> </Style> </Layer> + <Layer queryable="0"> + <Name>parent_layer</Name> + <Title>Parent Layer</Title> + <Layer queryable="1"> + <Name>child_layer</Name> + <Title>Child Layer</Title> + </Layer> + </Layer> </Layer> </Capability> </WMT_MS_Capabilities> -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/owslib.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel