/[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 2133 - (show annotations)
Wed Mar 24 19:31:54 2004 UTC (20 years, 11 months ago) by joey
File MIME type: text/x-python
File size: 17512 byte(s)
Finally added the XML parser for the GetCapabilities response.

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