/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/load.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/load.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1844 - (hide annotations)
Tue Oct 21 10:49:44 2003 UTC (21 years, 4 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 22605 byte(s)
(SessionLoader.__init__): Also accept the
thuban-1.0-dev.dtd namespace
(SessionLoader.check_attrs): Allow a callable object as conversion
too
(SessionLoader.start_projection, SessionLoader.end_projection)
(SessionLoader.start_parameter): Handle the epsg attribute and
rename a few instance variables to lower case

1 bh 723 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4 bh 267 # Bernhard Herzog <[email protected]>
5 jonathan 413 # Jonathan Coles <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with GRASS for details.
9    
10     """
11     Parser for thuban session files.
12     """
13    
14     __version__ = "$Revision$"
15    
16 bh 723 import string, os
17 bh 267
18     import xml.sax
19     import xml.sax.handler
20 jonathan 526 from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
21 bh 267
22 jan 374 from Thuban import _
23 jonathan 413
24 jonathan 473 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
25     FIELDTYPE_STRING
26    
27 jonathan 1339 from Thuban.Model.color import Color, Transparent
28    
29 bh 6 from Thuban.Model.session import Session
30     from Thuban.Model.map import Map
31 jonathan 930 from Thuban.Model.layer import Layer, RasterLayer
32 bh 6 from Thuban.Model.proj import Projection
33 jonathan 874 from Thuban.Model.range import Range
34 jonathan 413 from Thuban.Model.classification import Classification, \
35 jonathan 439 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
36     ClassGroupProperties
37 bh 1268 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
38     from Thuban.Model.table import DBFTable
39     from Thuban.Model.transientdb import TransientJoinedTable
40 bh 6
41 bh 1646 from Thuban.Model.xmlreader import XMLReader
42     import resource
43    
44     import postgisdb
45    
46 bh 1268 class LoadError(Exception):
47    
48 bh 1646 """Exception raised when the thuban file is corrupted
49 bh 6
50 bh 1646 Not all cases of corrupted thuban files will lead to this exception
51     but those that are found by checks in the loading code itself are.
52     """
53    
54    
55     class LoadCancelled(Exception):
56    
57     """Exception raised to indicate that loading was interrupted by the user"""
58    
59    
60 bh 267 def parse_color(color):
61     """Return the color object for the string color.
62 bh 6
63 bh 267 Color may be either 'None' or of the form '#RRGGBB' in the usual
64     HTML color notation
65 bh 6 """
66     color = string.strip(color)
67     if color == "None":
68 jonathan 1339 result = Transparent
69 bh 6 elif color[0] == '#':
70     if len(color) == 7:
71     r = string.atoi(color[1:3], 16) / 255.0
72     g = string.atoi(color[3:5], 16) / 255.0
73     b = string.atoi(color[5:7], 16) / 255.0
74     result = Color(r, g, b)
75     else:
76 jan 374 raise ValueError(_("Invalid hexadecimal color specification %s")
77 bh 6 % color)
78     else:
79 jan 374 raise ValueError(_("Invalid color specification %s") % color)
80 bh 6 return result
81    
82 bh 1268 class AttrDesc:
83    
84     def __init__(self, name, required = False, default = "",
85     conversion = None):
86     if not isinstance(name, tuple):
87     fullname = (None, name)
88     else:
89     fullname = name
90     name = name[1]
91     self.name = name
92     self.fullname = fullname
93     self.required = required
94     self.default = default
95     self.conversion = conversion
96    
97     # set by the SessionLoader's check_attrs method
98     self.value = None
99    
100    
101 jonathan 706 class SessionLoader(XMLReader):
102 jonathan 694
103 bh 1646 def __init__(self, db_connection_callback = None):
104 jonathan 694 """Inititialize the Sax handler."""
105 jonathan 706 XMLReader.__init__(self)
106 jonathan 694
107 bh 1646 self.db_connection_callback = db_connection_callback
108    
109 jonathan 694 self.theSession = None
110     self.aMap = None
111     self.aLayer = None
112    
113 bh 1268 # Map ids used in the thuban file to the corresponding objects
114     # in the session
115     self.idmap = {}
116 jonathan 706
117 bh 1268 dispatchers = {
118     'session' : ("start_session", "end_session"),
119 bh 1646
120     'dbconnection': ("start_dbconnection", None),
121    
122     'dbshapesource': ("start_dbshapesource", None),
123 bh 1268 'fileshapesource': ("start_fileshapesource", None),
124     'derivedshapesource': ("start_derivedshapesource", None),
125     'filetable': ("start_filetable", None),
126     'jointable': ("start_jointable", None),
127    
128     'map' : ("start_map", "end_map"),
129     'projection' : ("start_projection", "end_projection"),
130     'parameter' : ("start_parameter", None),
131     'layer' : ("start_layer", "end_layer"),
132     'rasterlayer' : ("start_rasterlayer", "end_rasterlayer"),
133     'classification': ("start_classification", "end_classification"),
134     'clnull' : ("start_clnull", "end_clnull"),
135     'clpoint' : ("start_clpoint", "end_clpoint"),
136     'clrange' : ("start_clrange", "end_clrange"),
137     'cldata' : ("start_cldata", "end_cldata"),
138     'table' : ("start_table", "end_table"),
139     'labellayer' : ("start_labellayer", None),
140     'label' : ("start_label", None)}
141    
142 bh 1375 # all dispatchers should be used for the 0.8 and 0.9 namespaces too
143     for xmlns in ("http://thuban.intevation.org/dtds/thuban-0.8.dtd",
144 bh 1664 "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd",
145 bh 1844 "http://thuban.intevation.org/dtds/thuban-0.9.dtd",
146     "http://thuban.intevation.org/dtds/thuban-1.0-dev.dtd"):
147 bh 1375 for key, value in dispatchers.items():
148     dispatchers[(xmlns, key)] = value
149 bh 1268
150     XMLReader.AddDispatchers(self, dispatchers)
151    
152 bh 267 def start_session(self, name, qname, attrs):
153 bh 1268 self.theSession = Session(self.encode(attrs.get((None, 'title'),
154     None)))
155 bh 6
156 bh 267 def end_session(self, name, qname):
157     pass
158 bh 6
159 bh 1268 def check_attrs(self, element, attrs, descr):
160     """Check and convert some of the attributes of an element
161    
162     Parameters:
163     element -- The element name
164     attrs -- The attrs mapping as passed to the start_* methods
165     descr -- Sequence of attribute descriptions (AttrDesc instances)
166    
167     Return a dictionary containig normalized versions of the
168     attributes described in descr. The keys of that dictionary are
169     the name attributes of the attribute descriptions. The attrs
170     dictionary will not be modified.
171    
172     If the attribute is required, i.e. the 'required' attribute of
173     the descrtiption is true, but it is not in attrs, raise a
174     LoadError.
175    
176     If the attribute has a default value and it is not present in
177     attrs, use that default value as the value in the returned dict.
178    
179     If a conversion is specified, convert the value before putting
180     it into the returned dict. The following conversions are
181     available:
182    
183     'filename' -- The attribute is a filename.
184    
185     If the filename is a relative name, interpret
186     it relative to the directory containing the
187     .thuban file and make it an absolute name
188    
189     'shapestore' -- The attribute is the ID of a shapestore
190     defined earlier in the .thuban file. Look it
191     up self.idmap
192    
193     'table' -- The attribute is the ID of a table or shapestore
194     defined earlier in the .thuban file. Look it up
195     self.idmap. If it's the ID of a shapestore the
196     value will be the table of the shapestore.
197 bh 1646
198     'idref' -- The attribute is the id of an object defined
199     earlier in the .thuban file. Look it up self.idmap
200    
201     'ascii' -- The attribute is converted to a bytestring with
202     ascii encoding.
203 bh 1844
204     a callable -- The attribute value is passed to the callable
205     and the return value is used a as the converted
206     value
207 bh 1268 """
208     normalized = {}
209    
210     for d in descr:
211     if d.required and not attrs.has_key(d.fullname):
212 bh 1642 raise LoadError("Element %s requires an attribute %r"
213     % (element, d.name))
214 bh 1268 value = attrs.get(d.fullname, d.default)
215    
216 bh 1646 if d.conversion in ("idref", "shapesource"):
217 bh 1268 if value in self.idmap:
218     value = self.idmap[value]
219     else:
220     raise LoadError("Element %s requires an already defined ID"
221     " in attribute %r"
222     % (element, d.name))
223     elif d.conversion == "table":
224     if value in self.idmap:
225     value = self.idmap[value]
226     if isinstance(value, ShapefileStore):
227     value = value.Table()
228     else:
229     raise LoadError("Element %s requires an already defined ID"
230     " in attribute %r"
231     % (element, d.name))
232     elif d.conversion == "filename":
233     value = os.path.abspath(os.path.join(self.GetDirectory(),
234     value))
235 bh 1646 elif d.conversion == "ascii":
236     value = value.encode("ascii")
237 bh 1844 elif d.conversion:
238     # Assume it's a callable
239     value = d.conversion(value)
240 bh 1268
241     normalized[d.name] = value
242     return normalized
243    
244 bh 1646 def start_dbconnection(self, name, qname, attrs):
245     attrs = self.check_attrs(name, attrs,
246     [AttrDesc("id", True),
247     AttrDesc("dbtype", True),
248     AttrDesc("host", False, ""),
249     AttrDesc("port", False, ""),
250     AttrDesc("user", False, ""),
251     AttrDesc("dbname", True)])
252     ID = attrs["id"]
253     dbtype = attrs["dbtype"]
254     if dbtype != "postgis":
255     raise LoadError("dbtype %r not supported" % filetype)
256    
257     del attrs["id"]
258     del attrs["dbtype"]
259    
260     # Try to open the connection and if it fails ask the user for
261     # the correct parameters repeatedly.
262     # FIXME: it would be better not to insist on getting a
263     # connection here. We should handle this more like the raster
264     # images where the layers etc still are created but are not
265     # drawn in case Thuban can't use the data for various reasons
266     while 1:
267     try:
268     conn = postgisdb.PostGISConnection(**attrs)
269     break
270     except postgisdb.ConnectionError, val:
271     if self.db_connection_callback is not None:
272     attrs = self.db_connection_callback(attrs, str(val))
273     if attrs is None:
274     raise LoadCancelled
275     else:
276     raise
277    
278     self.idmap[ID] = conn
279     self.theSession.AddDBConnection(conn)
280    
281     def start_dbshapesource(self, name, qname, attrs):
282     attrs = self.check_attrs(name, attrs,
283     [AttrDesc("id", True),
284     AttrDesc("dbconn", True,
285     conversion = "idref"),
286     AttrDesc("tablename", True,
287     conversion = "ascii")])
288     ID = attrs["id"]
289     db = attrs["dbconn"]
290     tablename = attrs["tablename"]
291     self.idmap[ID] = self.theSession.OpenDBShapeStore(db, tablename)
292    
293 bh 1268 def start_fileshapesource(self, name, qname, attrs):
294     attrs = self.check_attrs(name, attrs,
295     [AttrDesc("id", True),
296     AttrDesc("filename", True,
297     conversion = "filename"),
298     AttrDesc("filetype", True)])
299     ID = attrs["id"]
300     filename = attrs["filename"]
301     filetype = attrs["filetype"]
302     if filetype != "shapefile":
303     raise LoadError("shapesource filetype %r not supported" % filetype)
304     self.idmap[ID] = self.theSession.OpenShapefile(filename)
305    
306     def start_derivedshapesource(self, name, qname, attrs):
307     attrs = self.check_attrs(name, attrs,
308     [AttrDesc("id", True),
309     AttrDesc("shapesource", True,
310     conversion = "shapesource"),
311     AttrDesc("table", True, conversion="table")])
312 bh 1282 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
313     self.theSession.AddShapeStore(store)
314     self.idmap[attrs["id"]] = store
315 bh 1268
316     def start_filetable(self, name, qname, attrs):
317     attrs = self.check_attrs(name, attrs,
318     [AttrDesc("id", True),
319     AttrDesc("title", True),
320     AttrDesc("filename", True,
321     conversion = "filename"),
322     AttrDesc("filetype")])
323     filetype = attrs["filetype"]
324     if filetype != "DBF":
325     raise LoadError("shapesource filetype %r not supported" % filetype)
326     table = DBFTable(attrs["filename"])
327     table.SetTitle(attrs["title"])
328     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
329    
330     def start_jointable(self, name, qname, attrs):
331     attrs = self.check_attrs(name, attrs,
332     [AttrDesc("id", True),
333     AttrDesc("title", True),
334     AttrDesc("left", True, conversion="table"),
335     AttrDesc("leftcolumn", True),
336     AttrDesc("right", True, conversion="table"),
337 bh 1375 AttrDesc("rightcolumn", True),
338    
339     # jointype is required for file
340     # version 0.9 but this attribute
341     # wasn't in the 0.8 version because of
342     # an oversight so we assume it's
343     # optional since we want to handle
344     # both file format versions here.
345     AttrDesc("jointype", False,
346     default="INNER")])
347    
348     jointype = attrs["jointype"]
349     if jointype == "LEFT OUTER":
350     outer_join = True
351     elif jointype == "INNER":
352     outer_join = False
353     else:
354     raise LoadError("jointype %r not supported" % jointype )
355 bh 1268 table = TransientJoinedTable(self.theSession.TransientDB(),
356     attrs["left"], attrs["leftcolumn"],
357 bh 1375 attrs["right"], attrs["rightcolumn"],
358     outer_join = outer_join)
359 bh 1268 table.SetTitle(attrs["title"])
360     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
361    
362 bh 267 def start_map(self, name, qname, attrs):
363     """Start a map."""
364 frank 1408 self.aMap = Map(self.encode(attrs.get((None, 'title'), None)))
365 bh 267
366     def end_map(self, name, qname):
367     self.theSession.AddMap(self.aMap)
368 jonathan 874 self.aMap = None
369 bh 267
370     def start_projection(self, name, qname, attrs):
371 bh 1844 attrs = self.check_attrs(name, attrs,
372     [AttrDesc("name", conversion=self.encode),
373     AttrDesc("epsg", default=None,
374     conversion=self.encode)])
375     self.projection_name = attrs["name"]
376     self.projection_epsg = attrs["epsg"]
377     self.projection_params = [ ]
378 bh 267
379     def end_projection(self, name, qname):
380 jonathan 874 if self.aLayer is not None:
381     obj = self.aLayer
382     elif self.aMap is not None:
383     obj = self.aMap
384     else:
385     assert False, "projection tag out of context"
386     pass
387    
388 bh 1844 obj.SetProjection(Projection(self.projection_params,
389     self.projection_name,
390     epsg = self.projection_epsg))
391 bh 267
392     def start_parameter(self, name, qname, attrs):
393     s = attrs.get((None, 'value'))
394     s = str(s) # we can't handle unicode in proj
395 bh 1844 self.projection_params.append(s)
396 bh 267
397     def start_layer(self, name, qname, attrs, layer_class = Layer):
398     """Start a layer
399    
400     Instantiate a layer of class layer_class from the attributes in
401     attrs which may be a dictionary as well as the normal SAX attrs
402     object and bind it to self.aLayer.
403     """
404 jonathan 874 title = self.encode(attrs.get((None, 'title'), ""))
405 bh 267 filename = attrs.get((None, 'filename'), "")
406 jonathan 694 filename = os.path.join(self.GetDirectory(), filename)
407 jonathan 930 filename = self.encode(filename)
408     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
409 bh 267 fill = parse_color(attrs.get((None, 'fill'), "None"))
410     stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
411     stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
412 bh 1268 if attrs.has_key((None, "shapestore")):
413     store = self.idmap[attrs[(None, "shapestore")]]
414     else:
415     store = self.theSession.OpenShapefile(filename)
416     self.aLayer = layer_class(title, store,
417 bh 723 fill = fill, stroke = stroke,
418 jonathan 772 lineWidth = stroke_width,
419 jonathan 930 visible = visible)
420 bh 267
421     def end_layer(self, name, qname):
422     self.aMap.AddLayer(self.aLayer)
423 jonathan 874 self.aLayer = None
424 bh 267
425 jonathan 930 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
426     title = self.encode(attrs.get((None, 'title'), ""))
427     filename = attrs.get((None, 'filename'), "")
428     filename = os.path.join(self.GetDirectory(), filename)
429     filename = self.encode(filename)
430     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
431    
432     self.aLayer = layer_class(title, filename, visible = visible)
433    
434     def end_rasterlayer(self, name, qname):
435     self.aMap.AddLayer(self.aLayer)
436     self.aLayer = None
437    
438 jonathan 365 def start_classification(self, name, qname, attrs):
439 jonathan 465 field = attrs.get((None, 'field'), None)
440    
441     fieldType = attrs.get((None, 'field_type'), None)
442     dbFieldType = self.aLayer.GetFieldType(field)
443    
444     if fieldType != dbFieldType:
445     raise ValueError(_("xml field type differs from database!"))
446    
447     # setup conversion routines depending on the kind of data
448     # we will be seeing later on
449     if fieldType == FIELDTYPE_STRING:
450     self.conv = str
451     elif fieldType == FIELDTYPE_INT:
452     self.conv = lambda p: int(float(p))
453     elif fieldType == FIELDTYPE_DOUBLE:
454     self.conv = float
455    
456 bh 1452 self.aLayer.SetClassificationColumn(field)
457 jonathan 465
458 jonathan 365 def end_classification(self, name, qname):
459     pass
460    
461     def start_clnull(self, name, qname, attrs):
462 jonathan 439 self.cl_group = ClassGroupDefault()
463 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
464 jonathan 439 self.cl_prop = ClassGroupProperties()
465 jonathan 365
466     def end_clnull(self, name, qname):
467 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
468     self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
469     del self.cl_group, self.cl_prop
470 jonathan 365
471     def start_clpoint(self, name, qname, attrs):
472     attrib_value = attrs.get((None, 'value'), "0")
473    
474 bh 1452 field = self.aLayer.GetClassificationColumn()
475 jonathan 1428 if self.aLayer.GetFieldType(field) == FIELDTYPE_STRING:
476 bh 1417 value = self.encode(attrib_value)
477     else:
478     value = self.conv(attrib_value)
479 jonathan 439 self.cl_group = ClassGroupSingleton(value)
480 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
481 jonathan 439 self.cl_prop = ClassGroupProperties()
482 jonathan 413
483 jonathan 365
484     def end_clpoint(self, name, qname):
485 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
486 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
487 jonathan 439 del self.cl_group, self.cl_prop
488 jonathan 365
489     def start_clrange(self, name, qname, attrs):
490    
491 jonathan 874 range = attrs.get((None, 'range'), None)
492     # for backward compatibility (min/max are not saved)
493     min = attrs.get((None, 'min'), None)
494     max = attrs.get((None, 'max'), None)
495    
496 jonathan 365 try:
497 jonathan 874 if range is not None:
498     self.cl_group = ClassGroupRange(Range(range))
499     elif min is not None and max is not None:
500 jonathan 1354 self.cl_group = ClassGroupRange((self.conv(min),
501     self.conv(max)))
502 jonathan 874 else:
503     self.cl_group = ClassGroupRange(Range(None))
504    
505 jonathan 365 except ValueError:
506 jan 374 raise ValueError(_("Classification range is not a number!"))
507 jonathan 365
508 jonathan 439 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
509     self.cl_prop = ClassGroupProperties()
510 jonathan 413
511 jonathan 365
512     def end_clrange(self, name, qname):
513 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
514 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
515 jonathan 439 del self.cl_group, self.cl_prop
516 jonathan 365
517     def start_cldata(self, name, qname, attrs):
518 jonathan 465 self.cl_prop.SetLineColor(
519     parse_color(attrs.get((None, 'stroke'), "None")))
520     self.cl_prop.SetLineWidth(
521 jonathan 390 int(attrs.get((None, 'stroke_width'), "0")))
522 jonathan 439 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
523 jonathan 365
524     def end_cldata(self, name, qname):
525     pass
526    
527 bh 267 def start_labellayer(self, name, qname, attrs):
528     self.aLayer = self.aMap.LabelLayer()
529    
530     def start_label(self, name, qname, attrs):
531     x = float(attrs[(None, 'x')])
532     y = float(attrs[(None, 'y')])
533 jonathan 874 text = self.encode(attrs[(None, 'text')])
534 bh 267 halign = attrs[(None, 'halign')]
535     valign = attrs[(None, 'valign')]
536     self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
537    
538     def characters(self, chars):
539     pass
540    
541    
542 bh 1646 def load_session(filename, db_connection_callback = None):
543     """Load a Thuban session from the file object file
544 jonathan 694
545 bh 1646 The db_connection_callback, if given should be a callable object
546     that can be called like this:
547     db_connection_callback(params, message)
548    
549     where params is a dictionary containing the known connection
550     parameters and message is a string with a message why the connection
551     failed. db_connection_callback should return a new dictionary with
552     corrected and perhaps additional parameters like a password or None
553     to indicate that the user cancelled.
554     """
555     handler = SessionLoader(db_connection_callback)
556 jonathan 706 handler.read(filename)
557 jonathan 694
558 bh 6 session = handler.theSession
559     # Newly loaded session aren't modified
560     session.UnsetModified()
561    
562     return session
563    

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26