/[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 2439 - (show annotations)
Thu Dec 9 10:37:44 2004 UTC (20 years, 3 months ago) by joey
File MIME type: text/x-python
File size: 17491 byte(s)
Dropped support for get_srs_discrepancies() since there are no
discrepancies anymore (was a thinko)

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