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