/[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 2173 - (show annotations)
Tue Apr 13 19:28:05 2004 UTC (20 years, 10 months ago) by joey
File MIME type: text/x-python
File size: 17804 byte(s)
Added support for oldstyle (WMS 1.0 apparently) image format specification.

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