/[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 2383 - (show annotations)
Thu Oct 7 14:19:48 2004 UTC (20 years, 5 months ago) by jan
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 15602 byte(s)
(SessionSaver.write_classification): Write attribute 'size' for cldata
when the shape layer is of point type. This also now make the
test_load.py tests happy.

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