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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1638 - (hide annotations)
Fri Aug 22 18:19:14 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 13933 byte(s)
Implement saving a session with a postgis connection

* Resources/XML/thuban-0.9.dtd (dbconnection, dbshapesource) New
elements for database connections and shapestores using db
connections
(session): Add the dbconnections to the content model

* Thuban/Model/save.py (SessionSaver.write_db_connections): New.
Write the db connections
(SessionSaver.write_session): Call write_db_connections to write
the connection before the data sources
(SessionSaver.write_data_containers): Handle postgis shapestores

* test/test_save.py (SaveSessionTest.thubanids)
(SaveSessionTest.thubanidrefs): Update for new DTD
(SaveSessionTest.test_save_postgis): New. Test saving a session
with postgis connections

* Thuban/Model/postgisdb.py (PostGISTable.DBConnection)
(PostGISTable.TableName): New accessor methods for the connection
and table name

* test/test_postgis_db.py (TestPostGISTable.test_dbconn)
(TestPostGISTable.test_dbname): New methods to test the new
PostGISConnection methods

1 bh 454 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4     # Bernhard Herzog <[email protected]>
5 jonathan 414 # Jonathan Coles <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with Thuban for details.
9    
10     """
11     Functions to save a session to a file
12     """
13    
14     __version__ = "$Revision$"
15    
16     import os
17    
18 bh 201 import Thuban.Lib.fileutil
19 bh 6
20 jonathan 932 from Thuban.Model.layer import Layer, RasterLayer
21    
22 jonathan 876 from Thuban.Model.classification import \
23     ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
24 bh 1268 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
25 bh 1417 from Thuban.Model.table import DBFTable, FIELDTYPE_STRING
26 bh 1268 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
27 jonathan 429
28 jonathan 1160 from Thuban.Model.xmlwriter import XMLWriter
29 bh 1638 from postgisdb import PostGISConnection, PostGISShapeStore
30 jonathan 366
31 bh 201 def relative_filename(dir, filename):
32     """Return a filename relative to dir for the absolute file name absname.
33    
34     This is almost the same as the function in fileutil, except that dir
35     can be an empty string in which case filename will be returned
36     unchanged.
37     """
38     if dir:
39     return Thuban.Lib.fileutil.relative_filename(dir, filename)
40     else:
41     return filename
42    
43 bh 1268
44     def sort_data_stores(stores):
45     """Return a topologically sorted version of the sequence of data containers
46    
47     The list is sorted so that data containers that depend on other data
48     containers have higher indexes than the containers they depend on.
49     """
50     if not stores:
51     return []
52     processed = {}
53     result = []
54     todo = stores[:]
55     while todo:
56     # It doesn't really matter which if the items of todo is
57     # processed next, but if we take the first one, the order is
58     # preserved to some degree which makes writing some of the test
59     # cases easier.
60     container = todo.pop(0)
61     if id(container) in processed:
62     continue
63     deps = [dep for dep in container.Dependencies()
64     if id(dep) not in processed]
65     if deps:
66     todo.append(container)
67     todo.extend(deps)
68     else:
69     result.append(container)
70     processed[id(container)] = 1
71     return result
72    
73    
74 jonathan 710 class SessionSaver(XMLWriter):
75 bh 268
76 jonathan 697 """Class to serialize a session into an XML file.
77    
78     Applications built on top of Thuban may derive from this class and
79     override or extend the methods to save additional information. This
80     additional information should take the form of additional attributes
81     or elements whose names are prefixed with a namespace. To define a
82     namespace derived classes should extend the write_session method to
83     pass the namespaces to the default implementation.
84     """
85    
86    
87     def __init__(self, session):
88 jonathan 710 XMLWriter.__init__(self)
89 jonathan 697 self.session = session
90 bh 1268 # Map object ids to the ids used in the thuban files
91     self.idmap = {}
92 jonathan 697
93 bh 1268 def get_id(self, obj):
94     """Return the id used in the thuban file for the object obj"""
95     return self.idmap.get(id(obj))
96    
97     def define_id(self, obj, value = None):
98     if value is None:
99     value = "D" + str(id(obj))
100     self.idmap[id(obj)] = value
101     return value
102    
103     def has_id(self, obj):
104     return self.idmap.has_key(id(obj))
105    
106 jonathan 697 def write(self, file_or_filename):
107 jonathan 710 XMLWriter.write(self, file_or_filename)
108 jonathan 697
109 bh 1375 self.write_header("session", "thuban-0.9.dtd")
110 jonathan 697 self.write_session(self.session)
111     self.close()
112    
113 bh 268 def write_session(self, session, attrs = None, namespaces = ()):
114     """Write the session and its contents
115    
116     By default, write a session element with the title attribute and
117     call write_map for each map contained in the session.
118    
119     The optional argument attrs is for additional attributes and, if
120     given, should be a mapping from attribute names to attribute
121     values. The values should not be XML-escaped yet.
122    
123     The optional argument namespaces, if given, should be a sequence
124     of (name, URI) pairs. The namespaces are written as namespace
125     attributes into the session element. This is mainly useful for
126     derived classes that need to store additional information in a
127     thuban session file.
128     """
129     if attrs is None:
130     attrs = {}
131     attrs["title"] = session.title
132     for name, uri in namespaces:
133     attrs["xmlns:" + name] = uri
134 bh 1268 # default name space
135     attrs["xmlns"] = \
136 bh 1375 "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd"
137 jonathan 366 self.open_element("session", attrs)
138 bh 1638 self.write_db_connections(session)
139 bh 1268 self.write_data_containers(session)
140 bh 268 for map in session.Maps():
141     self.write_map(map)
142 jonathan 366 self.close_element("session")
143 bh 268
144 bh 1638 def write_db_connections(self, session):
145     for conn in session.DBConnections():
146     if isinstance(conn, PostGISConnection):
147     self.write_element("dbconnection",
148     {"id": self.define_id(conn),
149     "dbtype": "postgis",
150     "host": conn.host,
151     "port": conn.port,
152     "user": conn.user,
153     "dbname": conn.dbname})
154     else:
155     raise ValueError("Can't handle db connection %r" % conn)
156    
157 bh 1268 def write_data_containers(self, session):
158     containers = sort_data_stores(session.DataContainers())
159     for container in containers:
160     if isinstance(container, AutoTransientTable):
161     # AutoTransientTable instances are invisible in the
162     # thuban files. They're only used internally. To make
163     # sure that containers depending on AutoTransientTable
164     # instances refer to the right real containers we give
165     # the AutoTransientTable instances the same id as the
166     # source they depend on.
167     self.define_id(container,
168     self.get_id(container.Dependencies()[0]))
169     continue
170    
171     idvalue = self.define_id(container)
172     if isinstance(container, ShapefileStore):
173     self.define_id(container.Table(), idvalue)
174     filename = relative_filename(self.dir, container.FileName())
175     self.write_element("fileshapesource",
176     {"id": idvalue, "filename": filename,
177     "filetype": "shapefile"})
178     elif isinstance(container, DerivedShapeStore):
179     shapesource, table = container.Dependencies()
180     self.write_element("derivedshapesource",
181     {"id": idvalue,
182     "shapesource": self.get_id(shapesource),
183     "table": self.get_id(table)})
184 bh 1638 elif isinstance(container, PostGISShapeStore):
185     conn = container.DBConnection()
186     self.write_element("dbshapesource",
187     {"id": idvalue,
188     "dbconn": self.get_id(conn),
189     "tablename": container.TableName()})
190 bh 1268 elif isinstance(container, DBFTable):
191     filename = relative_filename(self.dir, container.FileName())
192     self.write_element("filetable",
193     {"id": idvalue,
194     "title": container.Title(),
195     "filename": filename,
196     "filetype": "DBF"})
197     elif isinstance(container, TransientJoinedTable):
198     left, right = container.Dependencies()
199     left_field = container.left_field
200     right_field = container.right_field
201     self.write_element("jointable",
202     {"id": idvalue,
203     "title": container.Title(),
204     "right": self.get_id(right),
205     "rightcolumn": right_field,
206     "left": self.get_id(left),
207 bh 1375 "leftcolumn": left_field,
208     "jointype": container.JoinType()})
209 bh 1268 else:
210     raise ValueError("Can't handle container %r" % container)
211    
212    
213 bh 268 def write_map(self, map):
214     """Write the map and its contents.
215    
216     By default, write a map element element with the title
217     attribute, call write_projection to write the projection
218     element, call write_layer for each layer contained in the map
219     and finally call write_label_layer to write the label layer.
220     """
221 jonathan 876 self.open_element('map title="%s"' % self.encode(map.title))
222 bh 268 self.write_projection(map.projection)
223     for layer in map.Layers():
224     self.write_layer(layer)
225     self.write_label_layer(map.LabelLayer())
226 jonathan 366 self.close_element('map')
227 bh 6
228 bh 268 def write_projection(self, projection):
229     """Write the projection.
230     """
231     if projection and len(projection.params) > 0:
232 jonathan 876 self.open_element("projection", {"name": projection.GetName()})
233 bh 268 for param in projection.params:
234 jonathan 876 self.write_element('parameter value="%s"' %
235     self.encode(param))
236 jonathan 366 self.close_element("projection")
237 bh 268
238     def write_layer(self, layer, attrs = None):
239     """Write the layer.
240    
241     The optional argument attrs is for additional attributes and, if
242     given, should be a mapping from attribute names to attribute
243     values. The values should not be XML-escaped yet.
244     """
245 jonathan 391
246 bh 268 if attrs is None:
247     attrs = {}
248 jonathan 429
249 jonathan 1251 attrs["title"] = layer.title
250     attrs["visible"] = ("false", "true")[int(layer.Visible())]
251 bh 268
252 jonathan 932 if isinstance(layer, Layer):
253 bh 1268 attrs["shapestore"] = self.get_id(layer.ShapeStore())
254 jonathan 740
255 jonathan 932 lc = layer.GetClassification()
256 bh 1268 attrs["stroke"] = lc.GetDefaultLineColor().hex()
257 jonathan 932 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
258 bh 1268 attrs["fill"] = lc.GetDefaultFill().hex()
259 jonathan 740
260 jonathan 932 self.open_element("layer", attrs)
261     self.write_projection(layer.GetProjection())
262     self.write_classification(layer)
263     self.close_element("layer")
264     elif isinstance(layer, RasterLayer):
265 bh 1219 attrs["filename"] = relative_filename(self.dir, layer.filename)
266 jonathan 932 self.open_element("rasterlayer", attrs)
267     self.write_projection(layer.GetProjection())
268     self.close_element("rasterlayer")
269    
270 jonathan 366 def write_classification(self, layer, attrs = None):
271 jonathan 1251 """Write Classification information."""
272    
273 jonathan 366 if attrs is None:
274     attrs = {}
275    
276 jonathan 414 lc = layer.GetClassification()
277 jonathan 366
278 bh 1452 field = layer.GetClassificationColumn()
279    
280 jonathan 366 #
281 jonathan 1251 # there isn't a classification of anything so do nothing
282 jonathan 366 #
283     if field is None: return
284    
285     attrs["field"] = field
286 jonathan 1430 attrs["field_type"] = str(layer.GetFieldType(field))
287 jonathan 366 self.open_element("classification", attrs)
288    
289 jonathan 1251 for g in lc:
290     if isinstance(g, ClassGroupDefault):
291     open_el = 'clnull label="%s"' % self.encode(g.GetLabel())
292     close_el = 'clnull'
293 bh 1417 elif isinstance(g, ClassGroupSingleton):
294 jonathan 1430 if layer.GetFieldType(field) == FIELDTYPE_STRING:
295 bh 1417 value = self.encode(g.GetValue())
296     else:
297     value = str(g.GetValue())
298 jonathan 1251 open_el = 'clpoint label="%s" value="%s"' \
299 bh 1417 % (self.encode(g.GetLabel()), value)
300 jonathan 1251 close_el = 'clpoint'
301     elif isinstance(g, ClassGroupRange):
302     open_el = 'clrange label="%s" range="%s"' \
303     % (self.encode(g.GetLabel()), str(g.GetRange()))
304     close_el = 'clrange'
305     else:
306     assert False, _("Unsupported group type in classification")
307     continue
308 jonathan 429
309 jonathan 1251 data = g.GetProperties()
310     dict = {'stroke' : data.GetLineColor().hex(),
311     'stroke_width': str(data.GetLineWidth()),
312     'fill' : data.GetFill().hex()}
313 jonathan 429
314 jonathan 1251 self.open_element(open_el)
315     self.write_element("cldata", dict)
316     self.close_element(close_el)
317 jonathan 366
318     self.close_element("classification")
319    
320 bh 268 def write_label_layer(self, layer):
321     """Write the label layer.
322     """
323     labels = layer.Labels()
324 bh 6 if labels:
325 jonathan 366 self.open_element('labellayer')
326 bh 6 for label in labels:
327 jonathan 366 self.write_element(('label x="%g" y="%g" text="%s"'
328     ' halign="%s" valign="%s"')
329 jonathan 876 % (label.x, label.y,
330     self.encode(label.text),
331     label.halign,
332 bh 268 label.valign))
333 jonathan 366 self.close_element('labellayer')
334 bh 6
335 bh 268
336    
337     def save_session(session, file, saver_class = None):
338     """Save the session session to a file.
339    
340     The file argument may either be a filename or an open file object.
341    
342     The optional argument saver_class is the class to use to serialize
343     the session. By default or if it's None, the saver class will be
344 jonathan 697 SessionSaver.
345 bh 268
346     If writing the session is successful call the session's
347     UnsetModified method
348     """
349     if saver_class is None:
350 jonathan 697 saver_class = SessionSaver
351 bh 268 saver = saver_class(session)
352     saver.write(file)
353    
354 bh 6 # after a successful save consider the session unmodified.
355     session.UnsetModified()

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26