/[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 2145 - (show annotations)
Tue Mar 30 18:05:53 2004 UTC (20 years, 11 months ago) by joey
File MIME type: text/x-python
File size: 16138 byte(s)
Removed integrity test since it was only implemented due to a
misunderstanding.

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