/[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 2144 - (hide annotations)
Tue Mar 30 17:25:20 2004 UTC (20 years, 11 months ago) by joey
File MIME type: text/x-python
File size: 17762 byte(s)
Adjusted the getLayerSRS method to return the list of SRSes extracted
from <SRS> elements instead of <BoundingBox> elements.  Added a bit of
documentation as well.

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 joey 2144 - A server which has the ability to transform data to
389     different SRSes may choose not to provide an explicit
390     BoundingBox for every possible SRS available for each Layer.
391     Thus the list of <SRS> elements are authoritative.
392    
393 joey 2133 This implementation returns the list of SRS for the given
394     layer, calculated by looking at BoundingBoxes defined in the
395     named layer and all layers higher in the hierarchy up to the
396     root layer which weren't overwritten.
397     """
398     for i in range(len(self.layers)):
399     if 'name' in self.layers[i]:
400     if self.layers[i]['name'] == name:
401     pivot = i
402     break
403     else:
404     return []
405    
406     result = []
407     while pivot != -1:
408 joey 2144 if 'srs' in self.layers[pivot]:
409     for srs in self.layers[pivot]['srs']:
410 joey 2133 if srs not in result:
411     result.append(srs)
412     pivot = self.layers[pivot]['parent']
413    
414     return result
415    
416    
417     def getLayerLatLonBBox(self, name):
418     """
419     Returns a dictionary of the LatLonBoundingBox.
420    
421     ... for the named layer if it exists. The SRS is always
422     EPSG:4326 per convention. There should be at least one,
423     though, inherited from the root layer at least. If none
424     exists, the value None is returned.
425     """
426     for layer in self.layers:
427     if 'name' in layer:
428     if layer['name'] == name:
429     if 'llbbox' in layer:
430     return layer['llbbox']
431     else:
432     # No LatLonBoundingBox found in current layer,
433     # so traverse the hierarchy upwards and check
434     # again until there is one.
435     pivot = layer
436     while pivot['parent'] != -1:
437     pivot = self.layers[pivot['parent']]
438     if 'llbbox' in pivot:
439     return pivot['llbbox']
440    
441     return None
442    
443    
444     def getLayerBBox(self, name, srs):
445     """
446     Returns a dictionary of the BoundingBox.
447    
448     If no such BoundingBox exists, None is returned.
449    
450     The specification [OGC 01-068r3] says about BoundingBoxes:
451    
452     - Layers may have zero or more BoundingBox elements what are
453     either stated explicitly or inherited from a parent layer.
454    
455     - A layer may have multiple BoundingBox elements, but each one
456     shall state a different SRS.
457    
458     - A layer inherits any BoundingBoxes defined by its
459     parents.
460    
461     - A BoundingBox inherited from the parent layer for a
462     particular SRS is replaced by any declaration for the same
463     SRS in the current layer.
464    
465     - A BoundingBox in the child layer for a new SRS which is not
466     already declared by the parent, is added to the list of
467     BoundingBoxes for the child layer.
468    
469     - A single layer shall not contain more than one BoundingBox
470     element for the same SRS.
471     """
472     for layer in self.layers:
473     if 'name' in layer:
474     if layer['name'] == name:
475     if 'bbox' in layer:
476     if srs in layer['bbox']:
477     return layer['bbox'][srs]
478    
479     # No BoundingBox for the given SRS found in
480     # current layer, so traverse the hierarchy upwards
481     # and check again until there is one.
482     pivot = layer
483     while pivot['parent'] != -1:
484     pivot = self.layers[pivot['parent']]
485     if 'bbox' in pivot:
486     if srs in pivot['bbox']:
487     return pivot['bbox'][srs]
488    
489     return None
490    
491    
492     def isQueryable(self, name):
493     """
494     Returns the value of the queryable attribute of a layer.
495    
496     This attribute denotes whether this layer can be queried
497     through the GetFeatureInfo request. The default value is 0.
498    
499     The specification [OGC 01-068r3] this attribute can be
500     inherited.
501     """
502    
503     for layer in self.layers:
504     if 'name' in layer:
505     if layer['name'] == name:
506     try:
507     return layer['queryable']
508     except KeyError:
509     # No attribute in this layer, so traverse the
510     # hierarchy upwards
511     pivot = layer
512     while pivot['parent'] != -1:
513     pivot = self.layers[pivot['parent']]
514     if 'queryable' in pivot:
515     return pivot['queryable']
516     return 0
517    
518    
519     def get_srs_discrepancies(self):
520     """
521     Returns a list of layer identifications where the denoted SRS
522     values differ from the calculated ones through BoundingBox
523     elements.
524     """
525     return self.srs_discrepancies
526    
527    
528     if __name__ == "__main__":
529     print "This module cannot be executed standalone."
530    
531     import os
532    
533     try:
534     f = open("test/sample.xml", "r")
535     except IOError:
536     try:
537     f = open(os.path.dirname(__file__) + "/test/sample.xml", "r")
538     except IOError:
539     print "Cannot open sample.xml for reading"
540    
541     if f is not None:
542     sample = f.read();
543     f.close()
544    
545     ina = WMSCapabilitiesParser()
546     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