/[thuban]/trunk/thuban/Extensions/wms/parser.py
ViewVC logotype

Annotation of /trunk/thuban/Extensions/wms/parser.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2145 - (hide annotations)
Tue Mar 30 18:05:53 2004 UTC (20 years, 11 months ago) by joey
File MIME type: text/x-python
File size: 16138 byte(s)
Removed integrity test since it was only implemented due to a
misunderstanding.

1 joey 2133 # Copyright (c) 2004 by Intevation GmbH
2     # Authors:
3     # Martin Schulze <[email protected]>
4     #
5     # This program is free software; you can redistribute it and/or modify
6     # it under the terms of the GNU General Public License as published by
7     # the Free Software Foundation; either version 2 of the License, or
8     # (at your option) any later version.
9     #
10     # This program is distributed in the hope that it will be useful,
11     # but WITHOUT ANY WARRANTY; without even the implied warranty of
12     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13     # GNU General Public License for more details.
14     #
15     # You should have received a copy of the GNU General Public License
16     # along with this program; if not, write to the Free Software
17     # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18    
19     """
20     Inspect WMS Capabilities for later processing.
21    
22     Information should only be retrieved with the proper get*() methods.
23    
24     class WMSCapabilitiesParser:
25     __init__()
26    
27     grok(string)
28    
29     getTitle()
30     getAbstract()
31     getFees()
32     getAccessConstraints()
33     getFormats()
34     getLayers()
35     getSRS()
36    
37     getLayerTitle(layer)
38     getLayerSRS(layer)
39     getLayerLatLonBBox(layer)
40     getLayerBBox(layer, srs)
41    
42     isQueryable(layer)
43    
44     get_srs_discrepancies()
45     """
46    
47     __version__ = "$Revision$"
48     # $Source$
49     # $Id$
50    
51     import xml.dom.minidom
52    
53     from domutils import getElementsByName, getElementByName
54    
55     class WMSCapabilitiesParser:
56     """
57     Thuban class to parse capabilities supplied as large string.
58    
59     This class provides methods to parse and retrieve particular
60     information from the WMS capabilities XML. Information should
61     only be extracted by the respective get*() methods.
62     """
63    
64     layers = None
65     title = None
66     abstract = None
67     fees = None
68     access = None
69     formats = None
70     srs_discrepancies = None
71    
72    
73     def __init__(self):
74     """
75     Initialises an instance in this class.
76     """
77    
78     # Note that we must not initialise internal variables of the
79     # class in a mutable way or it will be shared among all
80     # instances. None is immutable, [] is not.
81     layers = []
82    
83    
84     def grok(self, data):
85     """
86     Parses the XML response to a WMS GetCapabilities request.
87    
88     Internal datastructure of the class will be filled.
89     Information should only be retrieved with the respective
90     get*() methods.
91     """
92     root = xml.dom.minidom.parseString(data).documentElement
93    
94     # Extract the title
95     foo = getElementByName(getElementByName(root, 'Service'), 'Title')
96     if foo:
97     self.title = foo.childNodes[0].data
98    
99     # Extract the abstract
100     foo = getElementByName(getElementByName(root, 'Service'), 'Abstract')
101     if foo and len(foo.childNodes[0].data):
102     self.abstract = foo.childNodes[0].data
103    
104     # Extract fees information
105     foo = getElementByName(getElementByName(root, 'Service'), 'Fees')
106     if foo and len(foo.childNodes[0].data) \
107     and lower(foo.childNodes[0].data) != 'none':
108     self.fees = foo.childNodes[0].data
109    
110     # Extract access information
111     foo = getElementByName(getElementByName(root, 'Service'),
112     'AccessConstraints')
113     if foo and len(foo.childNodes[0].data) \
114     and lower(foo.childNodes[0].data) != 'none':
115     self.access = foo.childNodes[0].data
116    
117     # Extract output format information
118     foo = getElementsByName(
119     getElementByName(getElementByName(getElementByName(
120     root, 'Capability'), 'Request'), 'GetMap'), 'Format')
121     self.formats = map((lambda i: i.childNodes[0].data), foo)
122    
123     # Extract layer names
124     self.layers = []
125     self.peekLayers(getElementByName(getElementByName(
126     root, 'Capability'), 'Layer'), -1)
127    
128    
129     def peekLayers(self, top, parent):
130     """
131     Inspect the provided DOM fragment referenced as top.
132    
133     This method will inspect all included layers and traverse the
134     tree recursively in order to fill the internal datastructure.
135    
136     Note that SRS other than EPSG:* are not yet supported,
137     especially there is no support for AUTO and NONE.
138     """
139    
140     index = len (self.layers)
141     self.layers.append({})
142     self.layers[index]['parent'] = parent
143    
144     for foo in top.attributes.keys():
145     if foo == 'queryable':
146     self.layers[index]['queryable'] \
147     = int(top.attributes.get(foo).nodeValue)
148    
149     foo = getElementByName(top, 'Title')
150     if foo and len(foo.childNodes[0].data):
151     self.layers[index]['title'] = foo.childNodes[0].data
152    
153     foo = getElementByName(top, 'Name')
154     if foo and len(foo.childNodes[0].data):
155     self.layers[index]['name'] = foo.childNodes[0].data
156    
157     # These values are only used for an integrity check
158     for foo in getElementsByName(top, 'SRS'):
159     for srs in foo.childNodes[0].data.split(" "):
160     if srs[0:5] == 'EPSG:':
161     srs = srs[5:]
162     try:
163     self.layers[index]['srs'].append(srs)
164     except KeyError:
165     self.layers[index]['srs'] = [srs]
166    
167     foo = getElementByName(top, 'LatLonBoundingBox')
168     if foo is not None:
169     self.layers[index]['llbbox'] = {}
170     for corner in ['minx', 'miny', 'maxx', 'maxy']:
171     self.layers[index]['llbbox'][corner] \
172     = foo.attributes.get(corner).nodeValue
173    
174     boxes = getElementsByName(top, 'BoundingBox')
175     if boxes != []:
176     self.layers[index]['bbox'] = {}
177     for foo in boxes:
178     srs = foo.attributes.get('SRS').nodeValue
179     if srs[0:5] == 'EPSG:':
180     srs = srs[5:]
181     self.layers[index]['bbox'][srs] = {}
182     for corner in ['minx', 'miny', 'maxx', 'maxy']:
183     self.layers[index]['bbox'][srs][corner] \
184     = foo.attributes.get(corner).nodeValue
185    
186     # Traverse subsidiary layers
187     sublayer = getElementsByName(top, 'Layer')
188     for l in sublayer:
189     self.peekLayers(l, index)
190    
191    
192     def getTitle(self):
193     """
194     Returns the main title of the WMS object.
195    
196     If no title is provided in the capabilities, an empty string
197     is returned.
198     """
199     if self.title is None:
200     return ''
201     else:
202     return self.title
203    
204    
205     def getAbstract(self):
206     """
207     Returns the abstract of the WMS object.
208    
209     If no abstract is provided in the capabilities, an empty
210     string is returned.
211     """
212     if self.abstract is None:
213     return ''
214     else:
215     return self.abstract
216    
217    
218     def getFees(self):
219     """
220     Returns the fees information of the WMS object.
221    
222     If no information is provided in the capabilities or if it is
223     set to 'none', an empty string is returned.
224     """
225     if self.fees is None:
226     return ''
227     else:
228     return self.fees
229    
230    
231     def getAccessConstraints(self):
232     """
233     Returns information about access constraints for the WMS object.
234    
235     If no information is provided in the capabilities or if it is
236     set to 'none', an empty string is returned.
237     """
238     if self.access is None:
239     return ''
240     else:
241     return self.access
242    
243    
244     def getFormats(self):
245     """
246     Returns a list of supported output formats.
247    
248     These are used in the GetMap request. This method will
249     default to 'image/jpeg' if no format is recognised in XML
250     Capabilities, assuming that JPEG will always be supported on
251     the server side.
252     """
253     if self.formats is None:
254     return ['image/jpeg']
255     else:
256     return self.formats
257    
258     def getLayers(self):
259     """
260     Returns a list of layer names.
261    
262     Only named layers will be returned, since a layer may have a
263     title but doesn't have to have a name associated to it as
264     well. If no layers were found, an empty list is returned.
265     """
266     result = []
267     for layer in self.layers:
268     if 'name' in layer:
269     result.append(layer['name'])
270    
271     return result
272    
273    
274     def getSRS(self):
275     """
276     Returns the root list of spatial reference systems (SRS).
277    
278     This list is taken from the root layer. Those SRS are common
279     to all subsidiary layers. If no SRS are common to all layers,
280     an empty list is returned. If no layers were detected, an
281     empty list is returned as well.
282     """
283     if len(self.layers) == 0:
284     return []
285    
286     # index 0 denotes the root layer by design
287     if 'srs' in self.layers[0].keys():
288     return self.layers[0]['srs']
289    
290    
291     def getLayerTitle(self, name):
292     """
293     Returns the title of the named layer.
294    
295     If no such title or no such layer exists, an empty string is
296     returned.
297     """
298     for layer in self.layers:
299     if 'name' in layer:
300     if layer['name'] == name:
301     if 'title' in layer:
302     return layer['title']
303    
304     return ''
305    
306    
307     def getLayerSRS(self, name):
308     """
309     Returns a list of spacial reference systems (SRS).
310    
311     The SRS are specified by the European Petroleum Survey Group
312     (EPSG). There should be at least one SRS per layer, though.
313     The prefix 'EPSG:' will be stripped. If none exists, an empty
314     list is returned.
315    
316     The specification [OGC 01-068r3] says about inheritance of
317     SRS:
318    
319     - Every layer is available in one or more SRS (or in an
320     undefined SRS)
321    
322     - Geographic information whose SRS is undefined
323     (e.g. digitised historical maps) shall use 'NONE'
324     (case-sensitive). This implementation does not support
325     this.
326    
327     - Every layer shall have at least one SRS element that is
328     either stated explicitly or inherited from a parent layer.
329    
330     - The root layer element shall include a sequence of zero or
331     more SRS elements listing all SRS which are common for to
332     all subsidiary layers.
333    
334     - Layers may optionally add to the global SRS list, or to the
335     list inherited from a parent layer.
336    
337 joey 2144 - A server which has the ability to transform data to
338     different SRSes may choose not to provide an explicit
339     BoundingBox for every possible SRS available for each Layer.
340     Thus the list of <SRS> elements are authoritative.
341    
342 joey 2133 This implementation returns the list of SRS for the given
343     layer, calculated by looking at BoundingBoxes defined in the
344     named layer and all layers higher in the hierarchy up to the
345     root layer which weren't overwritten.
346     """
347     for i in range(len(self.layers)):
348     if 'name' in self.layers[i]:
349     if self.layers[i]['name'] == name:
350     pivot = i
351     break
352     else:
353     return []
354    
355     result = []
356     while pivot != -1:
357 joey 2144 if 'srs' in self.layers[pivot]:
358     for srs in self.layers[pivot]['srs']:
359 joey 2133 if srs not in result:
360     result.append(srs)
361     pivot = self.layers[pivot]['parent']
362    
363     return result
364    
365    
366     def getLayerLatLonBBox(self, name):
367     """
368     Returns a dictionary of the LatLonBoundingBox.
369    
370     ... for the named layer if it exists. The SRS is always
371     EPSG:4326 per convention. There should be at least one,
372     though, inherited from the root layer at least. If none
373     exists, the value None is returned.
374     """
375     for layer in self.layers:
376     if 'name' in layer:
377     if layer['name'] == name:
378     if 'llbbox' in layer:
379     return layer['llbbox']
380     else:
381     # No LatLonBoundingBox found in current layer,
382     # so traverse the hierarchy upwards and check
383     # again until there is one.
384     pivot = layer
385     while pivot['parent'] != -1:
386     pivot = self.layers[pivot['parent']]
387     if 'llbbox' in pivot:
388     return pivot['llbbox']
389    
390     return None
391    
392    
393     def getLayerBBox(self, name, srs):
394     """
395     Returns a dictionary of the BoundingBox.
396    
397     If no such BoundingBox exists, None is returned.
398    
399     The specification [OGC 01-068r3] says about BoundingBoxes:
400    
401     - Layers may have zero or more BoundingBox elements what are
402     either stated explicitly or inherited from a parent layer.
403    
404     - A layer may have multiple BoundingBox elements, but each one
405     shall state a different SRS.
406    
407     - A layer inherits any BoundingBoxes defined by its
408     parents.
409    
410     - A BoundingBox inherited from the parent layer for a
411     particular SRS is replaced by any declaration for the same
412     SRS in the current layer.
413    
414     - A BoundingBox in the child layer for a new SRS which is not
415     already declared by the parent, is added to the list of
416     BoundingBoxes for the child layer.
417    
418     - A single layer shall not contain more than one BoundingBox
419     element for the same SRS.
420     """
421     for layer in self.layers:
422     if 'name' in layer:
423     if layer['name'] == name:
424     if 'bbox' in layer:
425     if srs in layer['bbox']:
426     return layer['bbox'][srs]
427    
428     # No BoundingBox for the given SRS found in
429     # current layer, so traverse the hierarchy upwards
430     # and check again until there is one.
431     pivot = layer
432     while pivot['parent'] != -1:
433     pivot = self.layers[pivot['parent']]
434     if 'bbox' in pivot:
435     if srs in pivot['bbox']:
436     return pivot['bbox'][srs]
437    
438     return None
439    
440    
441     def isQueryable(self, name):
442     """
443     Returns the value of the queryable attribute of a layer.
444    
445     This attribute denotes whether this layer can be queried
446     through the GetFeatureInfo request. The default value is 0.
447    
448     The specification [OGC 01-068r3] this attribute can be
449     inherited.
450     """
451    
452     for layer in self.layers:
453     if 'name' in layer:
454     if layer['name'] == name:
455     try:
456     return layer['queryable']
457     except KeyError:
458     # No attribute in this layer, so traverse the
459     # hierarchy upwards
460     pivot = layer
461     while pivot['parent'] != -1:
462     pivot = self.layers[pivot['parent']]
463     if 'queryable' in pivot:
464     return pivot['queryable']
465     return 0
466    
467    
468     def get_srs_discrepancies(self):
469     """
470     Returns a list of layer identifications where the denoted SRS
471     values differ from the calculated ones through BoundingBox
472     elements.
473     """
474     return self.srs_discrepancies
475    
476    
477     if __name__ == "__main__":
478     print "This module cannot be executed standalone."
479    
480     import os
481    
482     try:
483     f = open("test/sample.xml", "r")
484     except IOError:
485     try:
486     f = open(os.path.dirname(__file__) + "/test/sample.xml", "r")
487     except IOError:
488     print "Cannot open sample.xml for reading"
489    
490     if f is not None:
491     sample = f.read();
492     f.close()
493    
494     ina = WMSCapabilitiesParser()
495     ina.grok(sample)

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26