/[thuban]/branches/WIP-pyshapelib-bramz/Extensions/wms/parser.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Extensions/wms/parser.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2155 - (show annotations)
Sat Apr 10 19:12:41 2004 UTC (20 years, 11 months ago) by joey
Original Path: trunk/thuban/Extensions/wms/parser.py
File MIME type: text/x-python
File size: 16192 byte(s)
Whoops, it's "foo".lower() and not lower(foo) without lower imported
from strings or something.

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