/[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 2170 - (show annotations)
Tue Apr 13 17:21:19 2004 UTC (20 years, 10 months ago) by joey
File MIME type: text/x-python
File size: 17277 byte(s)
Looks like I misinterpreted the specs.

First of all, a <BoundingBox> element does not have to be specified.
This could cause the program to fail if later a bbox will be requested
which wasn't defined in the Capabilities XML.

Secondly, since EPSG:3426 is the implicit spatial reference system for
<LatLonBoundingBox> its values should be returned if the client
requests a bounding box for SRS EPSG:3426, even if no <BoundingBox>
was defined for EPSG:3426.

Therefore whenever a native BoundingBox request cannot be fulfilled,
check whether the requested SRS is EPSG:3426, in which case return the
LatLonBoundingBox values.

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