/[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 2170 - (hide annotations)
Tue Apr 13 17:21:19 2004 UTC (20 years, 10 months ago) by joey
Original Path: trunk/thuban/Extensions/wms/parser.py
File MIME type: text/x-python
File size: 17277 byte(s)
Looks like I misinterpreted the specs.

First of all, a <BoundingBox> element does not have to be specified.
This could cause the program to fail if later a bbox will be requested
which wasn't defined in the Capabilities XML.

Secondly, since EPSG:3426 is the implicit spatial reference system for
<LatLonBoundingBox> its values should be returned if the client
requests a bounding box for SRS EPSG:3426, even if no <BoundingBox>
was defined for EPSG:3426.

Therefore whenever a native BoundingBox request cannot be fulfilled,
check whether the requested SRS is EPSG:3426, in which case return the
LatLonBoundingBox values.

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