/[thuban]/trunk/thuban/Extensions/wms/parser.py
ViewVC logotype

Contents of /trunk/thuban/Extensions/wms/parser.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2165 - (show 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 # 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 from Thuban import _
56
57 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 error = None
74
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 self.error = []
85
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 xml_dom = xml.dom.minidom.parseString(data)
96 root = xml_dom.documentElement
97
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 and foo.childNodes[0].data.lower() != 'none':
112 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 and foo.childNodes[0].data.lower() != 'none':
119 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 xml_dom.unlink()
133
134
135 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 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
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 - 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 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 if 'srs' in self.layers[pivot]:
368 for srs in self.layers[pivot]['srs']:
369 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 sample = "test/sample.xml"
493 try:
494 f = open(sample, "r")
495 except IOError:
496 try:
497 f = open(os.path.dirname(__file__) + "/" + sample, "r")
498 except IOError:
499 print "Cannot open %s for reading" % sample
500
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