/[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 2165 - (hide annotations)
Tue Apr 13 16:42:48 2004 UTC (20 years, 10 months ago) by joey
File MIME type: text/x-python
File size: 16464 byte(s)
Added support for error messages during grok().  They will be
aggregated in an array and may be displayed later.  We may have to add
a classification "Warning" and "Error" to this.  That requires more
experience, though, since not every error may be lethal.

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