/[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 2616 - (show annotations)
Fri May 6 14:17:29 2005 UTC (19 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 15935 byte(s)
(SessionSaver.write_layer): Added code to save the opacity and mask type of a
layer.

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