/[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 2144 - (show annotations)
Tue Mar 30 17:25:20 2004 UTC (20 years, 11 months ago) by joey
File MIME type: text/x-python
File size: 17762 byte(s)
Adjusted the getLayerSRS method to return the list of SRSes extracted
from <SRS> elements instead of <BoundingBox> elements.  Added a bit of
documentation as well.

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