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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2036 - (show annotations)
Mon Dec 22 17:49:43 2003 UTC (21 years, 2 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 15072 byte(s)
* setup.py (setup call): 1.0.0, yeah!

* Thuban/version.py (longversion): 1.0.0, yeah!

* Thuban/Model/load.py (SessionLoader.__init__): Accept the
1.0.0 namespace too

* Thuban/Model/save.py (SessionSaver.write_session): Save with
1.0.0 namespace

* test/test_load.py (LoadSessionTest.dtd)
(TestSingleLayer.file_contents)
(TestNonAsciiColumnName.file_contents)
(TestLayerVisibility.file_contents)
(TestClassification.file_contents, TestLabels.file_contents)
(TestLayerProjection.file_contents)
(TestRasterLayer.file_contents, TestJoinedTable.file_contents)
(TestLabelLayer.file_contents, TestPostGISLayer.file_contents)
(TestPostGISLayerPassword.file_contents)
(TestLoadError.file_contents, TestLoadError.test): Update for
1.0.0 namespace

* test/test_save.py (SaveSessionTest.dtd)
(SaveSessionTest.testEmptySession)
(SaveSessionTest.testSingleLayer)
(SaveSessionTest.testLayerProjection)
(SaveSessionTest.testRasterLayer)
(SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.test_dbf_table)
(SaveSessionTest.test_joined_table)
(SaveSessionTest.test_save_postgis): Update for 1.0.0 namespace

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