/[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 2133 - (hide annotations)
Wed Mar 24 19:31:54 2004 UTC (20 years, 11 months ago) by joey
File MIME type: text/x-python
File size: 17512 byte(s)
Finally added the XML parser for the GetCapabilities response.

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     # Check for integrity
187     self.checkLayerSRS(index)
188    
189     # Traverse subsidiary layers
190     sublayer = getElementsByName(top, 'Layer')
191     for l in sublayer:
192     self.peekLayers(l, index)
193    
194    
195     def checkLayerSRS(self, index):
196     """
197     Checks the integrity of the underlying XML data.
198    
199     This is done by comparing the <SRS> elements with the
200     calculated list from the BoundingBox elements.
201    
202     index -- position in the layers array to check
203     """
204    
205     pivot = index
206     calculated = []
207     while pivot != -1:
208     if 'bbox' in self.layers[pivot]:
209     for srs in self.layers[pivot]['bbox'].keys():
210     if srs not in calculated:
211     calculated.append(srs)
212     pivot = self.layers[pivot]['parent']
213    
214     pivot = index
215     specified = []
216     while pivot != -1:
217     if 'srs' in self.layers[pivot]:
218     for srs in self.layers[pivot]['srs']:
219     if srs not in specified:
220     specified.append(srs)
221     pivot = self.layers[pivot]['parent']
222    
223     equal = True
224     # Check for same number of elements
225     if len(calculated) != len(specified):
226     equal = False
227    
228     # Loop through all elements for existance
229     for elm in calculated:
230     if elm not in specified:
231     equal = False
232    
233     if not equal:
234     if self.srs_discrepancies is None:
235     self.srs_discrepancies = []
236     if 'name' in self.layers[index]:
237     id = "name:%s" % self.layers[index]['name']
238     else:
239     id = "title:%s" % self.layers[index]['title']
240     self.srs_discrepancies.append(id)
241    
242    
243     def getTitle(self):
244     """
245     Returns the main title of the WMS object.
246    
247     If no title is provided in the capabilities, an empty string
248     is returned.
249     """
250     if self.title is None:
251     return ''
252     else:
253     return self.title
254    
255    
256     def getAbstract(self):
257     """
258     Returns the abstract of the WMS object.
259    
260     If no abstract is provided in the capabilities, an empty
261     string is returned.
262     """
263     if self.abstract is None:
264     return ''
265     else:
266     return self.abstract
267    
268    
269     def getFees(self):
270     """
271     Returns the fees information of the WMS object.
272    
273     If no information is provided in the capabilities or if it is
274     set to 'none', an empty string is returned.
275     """
276     if self.fees is None:
277     return ''
278     else:
279     return self.fees
280    
281    
282     def getAccessConstraints(self):
283     """
284     Returns information about access constraints for the WMS object.
285    
286     If no information is provided in the capabilities or if it is
287     set to 'none', an empty string is returned.
288     """
289     if self.access is None:
290     return ''
291     else:
292     return self.access
293    
294    
295     def getFormats(self):
296     """
297     Returns a list of supported output formats.
298    
299     These are used in the GetMap request. This method will
300     default to 'image/jpeg' if no format is recognised in XML
301     Capabilities, assuming that JPEG will always be supported on
302     the server side.
303     """
304     if self.formats is None:
305     return ['image/jpeg']
306     else:
307     return self.formats
308    
309     def getLayers(self):
310     """
311     Returns a list of layer names.
312    
313     Only named layers will be returned, since a layer may have a
314     title but doesn't have to have a name associated to it as
315     well. If no layers were found, an empty list is returned.
316     """
317     result = []
318     for layer in self.layers:
319     if 'name' in layer:
320     result.append(layer['name'])
321    
322     return result
323    
324    
325     def getSRS(self):
326     """
327     Returns the root list of spatial reference systems (SRS).
328    
329     This list is taken from the root layer. Those SRS are common
330     to all subsidiary layers. If no SRS are common to all layers,
331     an empty list is returned. If no layers were detected, an
332     empty list is returned as well.
333     """
334     if len(self.layers) == 0:
335     return []
336    
337     # index 0 denotes the root layer by design
338     if 'srs' in self.layers[0].keys():
339     return self.layers[0]['srs']
340    
341    
342     def getLayerTitle(self, name):
343     """
344     Returns the title of the named layer.
345    
346     If no such title or no such layer exists, an empty string is
347     returned.
348     """
349     for layer in self.layers:
350     if 'name' in layer:
351     if layer['name'] == name:
352     if 'title' in layer:
353     return layer['title']
354    
355     return ''
356    
357    
358     def getLayerSRS(self, name):
359     """
360     Returns a list of spacial reference systems (SRS).
361    
362     The SRS are specified by the European Petroleum Survey Group
363     (EPSG). There should be at least one SRS per layer, though.
364     The prefix 'EPSG:' will be stripped. If none exists, an empty
365     list is returned.
366    
367     The specification [OGC 01-068r3] says about inheritance of
368     SRS:
369    
370     - Every layer is available in one or more SRS (or in an
371     undefined SRS)
372    
373     - Geographic information whose SRS is undefined
374     (e.g. digitised historical maps) shall use 'NONE'
375     (case-sensitive). This implementation does not support
376     this.
377    
378     - Every layer shall have at least one SRS element that is
379     either stated explicitly or inherited from a parent layer.
380    
381     - The root layer element shall include a sequence of zero or
382     more SRS elements listing all SRS which are common for to
383     all subsidiary layers.
384    
385     - Layers may optionally add to the global SRS list, or to the
386     list inherited from a parent layer.
387    
388     This implementation returns the list of SRS for the given
389     layer, calculated by looking at BoundingBoxes defined in the
390     named layer and all layers higher in the hierarchy up to the
391     root layer which weren't overwritten.
392     """
393     for i in range(len(self.layers)):
394     if 'name' in self.layers[i]:
395     if self.layers[i]['name'] == name:
396     pivot = i
397     break
398     else:
399     return []
400    
401     result = []
402     while pivot != -1:
403     if 'bbox' in self.layers[pivot]:
404     for srs in self.layers[pivot]['bbox'].keys():
405     if srs not in result:
406     result.append(srs)
407     pivot = self.layers[pivot]['parent']
408    
409     return result
410    
411    
412     def getLayerLatLonBBox(self, name):
413     """
414     Returns a dictionary of the LatLonBoundingBox.
415    
416     ... for the named layer if it exists. The SRS is always
417     EPSG:4326 per convention. There should be at least one,
418     though, inherited from the root layer at least. If none
419     exists, the value None is returned.
420     """
421     for layer in self.layers:
422     if 'name' in layer:
423     if layer['name'] == name:
424     if 'llbbox' in layer:
425     return layer['llbbox']
426     else:
427     # No LatLonBoundingBox found in current layer,
428     # so traverse the hierarchy upwards and check
429     # again until there is one.
430     pivot = layer
431     while pivot['parent'] != -1:
432     pivot = self.layers[pivot['parent']]
433     if 'llbbox' in pivot:
434     return pivot['llbbox']
435    
436     return None
437    
438    
439     def getLayerBBox(self, name, srs):
440     """
441     Returns a dictionary of the BoundingBox.
442    
443     If no such BoundingBox exists, None is returned.
444    
445     The specification [OGC 01-068r3] says about BoundingBoxes:
446    
447     - Layers may have zero or more BoundingBox elements what are
448     either stated explicitly or inherited from a parent layer.
449    
450     - A layer may have multiple BoundingBox elements, but each one
451     shall state a different SRS.
452    
453     - A layer inherits any BoundingBoxes defined by its
454     parents.
455    
456     - A BoundingBox inherited from the parent layer for a
457     particular SRS is replaced by any declaration for the same
458     SRS in the current layer.
459    
460     - A BoundingBox in the child layer for a new SRS which is not
461     already declared by the parent, is added to the list of
462     BoundingBoxes for the child layer.
463    
464     - A single layer shall not contain more than one BoundingBox
465     element for the same SRS.
466     """
467     for layer in self.layers:
468     if 'name' in layer:
469     if layer['name'] == name:
470     if 'bbox' in layer:
471     if srs in layer['bbox']:
472     return layer['bbox'][srs]
473    
474     # No BoundingBox for the given SRS found in
475     # current layer, so traverse the hierarchy upwards
476     # and check again until there is one.
477     pivot = layer
478     while pivot['parent'] != -1:
479     pivot = self.layers[pivot['parent']]
480     if 'bbox' in pivot:
481     if srs in pivot['bbox']:
482     return pivot['bbox'][srs]
483    
484     return None
485    
486    
487     def isQueryable(self, name):
488     """
489     Returns the value of the queryable attribute of a layer.
490    
491     This attribute denotes whether this layer can be queried
492     through the GetFeatureInfo request. The default value is 0.
493    
494     The specification [OGC 01-068r3] this attribute can be
495     inherited.
496     """
497    
498     for layer in self.layers:
499     if 'name' in layer:
500     if layer['name'] == name:
501     try:
502     return layer['queryable']
503     except KeyError:
504     # No attribute in this layer, so traverse the
505     # hierarchy upwards
506     pivot = layer
507     while pivot['parent'] != -1:
508     pivot = self.layers[pivot['parent']]
509     if 'queryable' in pivot:
510     return pivot['queryable']
511     return 0
512    
513    
514     def get_srs_discrepancies(self):
515     """
516     Returns a list of layer identifications where the denoted SRS
517     values differ from the calculated ones through BoundingBox
518     elements.
519     """
520     return self.srs_discrepancies
521    
522    
523     if __name__ == "__main__":
524     print "This module cannot be executed standalone."
525    
526     import os
527    
528     try:
529     f = open("test/sample.xml", "r")
530     except IOError:
531     try:
532     f = open(os.path.dirname(__file__) + "/test/sample.xml", "r")
533     except IOError:
534     print "Cannot open sample.xml for reading"
535    
536     if f is not None:
537     sample = f.read();
538     f.close()
539    
540     ina = WMSCapabilitiesParser()
541     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