/[thuban]/branches/WIP-pyshapelib-bramz/Extensions/wms/parser.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Extensions/wms/parser.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2439 - (hide annotations)
Thu Dec 9 10:37:44 2004 UTC (20 years, 3 months ago) by joey
Original Path: trunk/thuban/Extensions/wms/parser.py
File MIME type: text/x-python
File size: 17491 byte(s)
Dropped support for get_srs_discrepancies() since there are no
discrepancies anymore (was a thinko)

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