/[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 2384 - (show annotations)
Thu Oct 7 14:24:24 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: 15613 byte(s)
put Id and Source to the usual place

1 # Copyright (c) 2001-2004 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, ShapefileStore, \
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
94 class SessionSaver(XMLWriter):
95
96 """Class to serialize a session into an XML file.
97
98 Applications built on top of Thuban may derive from this class and
99 override or extend the methods to save additional information. This
100 additional information should take the form of additional attributes
101 or elements whose names are prefixed with a namespace. To define a
102 namespace derived classes should extend the write_session method to
103 pass the namespaces to the default implementation.
104 """
105
106
107 def __init__(self, session):
108 XMLWriter.__init__(self)
109 self.session = session
110 # Map object ids to the ids used in the thuban files
111 self.idmap = {}
112
113 def get_id(self, obj):
114 """Return the id used in the thuban file for the object obj"""
115 return self.idmap.get(id(obj))
116
117 def define_id(self, obj, value = None):
118 if value is None:
119 value = "D" + str(id(obj))
120 self.idmap[id(obj)] = value
121 return value
122
123 def has_id(self, obj):
124 return self.idmap.has_key(id(obj))
125
126 def prepare_filename(self, filename):
127 """Return the string to use when writing filename to the thuban file
128
129 The returned string is a unified version (only slashes as
130 directory separators, see unify_filename) of filename expressed
131 relative to the directory the .thuban file is written to.
132 """
133 return unify_filename(relative_filename(self.dir, filename))
134
135 def write(self, file_or_filename):
136 XMLWriter.write(self, file_or_filename)
137
138 self.write_header("session", "thuban-1.1.dtd")
139 self.write_session(self.session)
140 self.close()
141
142 def write_session(self, session, attrs = None, namespaces = ()):
143 """Write the session and its contents
144
145 By default, write a session element with the title attribute and
146 call write_map for each map contained in the session.
147
148 The optional argument attrs is for additional attributes and, if
149 given, should be a mapping from attribute names to attribute
150 values. The values should not be XML-escaped yet.
151
152 The optional argument namespaces, if given, should be a sequence
153 of (name, URI) pairs. The namespaces are written as namespace
154 attributes into the session element. This is mainly useful for
155 derived classes that need to store additional information in a
156 thuban session file.
157 """
158 if attrs is None:
159 attrs = {}
160 attrs["title"] = session.title
161 for name, uri in namespaces:
162 attrs["xmlns:" + name] = uri
163 # default name space
164 attrs["xmlns"] = \
165 "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"
166 self.open_element("session", attrs)
167 self.write_db_connections(session)
168 self.write_data_containers(session)
169 for map in session.Maps():
170 self.write_map(map)
171 self.close_element("session")
172
173 def write_db_connections(self, session):
174 for conn in session.DBConnections():
175 if isinstance(conn, PostGISConnection):
176 self.write_element("dbconnection",
177 {"id": self.define_id(conn),
178 "dbtype": "postgis",
179 "host": conn.host,
180 "port": conn.port,
181 "user": conn.user,
182 "dbname": conn.dbname})
183 else:
184 raise ValueError("Can't handle db connection %r" % conn)
185
186 def write_data_containers(self, session):
187 containers = sort_data_stores(session.DataContainers())
188 for container in containers:
189 if isinstance(container, AutoTransientTable):
190 # AutoTransientTable instances are invisible in the
191 # thuban files. They're only used internally. To make
192 # sure that containers depending on AutoTransientTable
193 # instances refer to the right real containers we give
194 # the AutoTransientTable instances the same id as the
195 # source they depend on.
196 self.define_id(container,
197 self.get_id(container.Dependencies()[0]))
198 continue
199
200 idvalue = self.define_id(container)
201 if isinstance(container, ShapefileStore):
202 self.define_id(container.Table(), idvalue)
203 filename = self.prepare_filename(container.FileName())
204 self.write_element("fileshapesource",
205 {"id": idvalue, "filename": filename,
206 "filetype": "shapefile"})
207 elif isinstance(container, DerivedShapeStore):
208 shapesource, table = container.Dependencies()
209 self.write_element("derivedshapesource",
210 {"id": idvalue,
211 "shapesource": self.get_id(shapesource),
212 "table": self.get_id(table)})
213 elif isinstance(container, PostGISShapeStore):
214 conn = container.DBConnection()
215 self.write_element("dbshapesource",
216 {"id": idvalue,
217 "dbconn": self.get_id(conn),
218 "tablename": container.TableName(),
219 "id_column": container.IDColumn().name,
220 "geometry_column":
221 container.GeometryColumn().name,
222 })
223 elif isinstance(container, DBFTable):
224 filename = self.prepare_filename(container.FileName())
225 self.write_element("filetable",
226 {"id": idvalue,
227 "title": container.Title(),
228 "filename": filename,
229 "filetype": "DBF"})
230 elif isinstance(container, TransientJoinedTable):
231 left, right = container.Dependencies()
232 left_field = container.left_field
233 right_field = container.right_field
234 self.write_element("jointable",
235 {"id": idvalue,
236 "title": container.Title(),
237 "right": self.get_id(right),
238 "rightcolumn": right_field,
239 "left": self.get_id(left),
240 "leftcolumn": left_field,
241 "jointype": container.JoinType()})
242 else:
243 raise ValueError("Can't handle container %r" % container)
244
245
246 def write_map(self, map):
247 """Write the map and its contents.
248
249 By default, write a map element element with the title
250 attribute, call write_projection to write the projection
251 element, call write_layer for each layer contained in the map
252 and finally call write_label_layer to write the label layer.
253 """
254 self.open_element('map title="%s"' % self.encode(map.title))
255 self.write_projection(map.projection)
256 for layer in map.Layers():
257 self.write_layer(layer)
258 self.write_label_layer(map.LabelLayer())
259 self.close_element('map')
260
261 def write_projection(self, projection):
262 """Write the projection.
263 """
264 if projection and len(projection.params) > 0:
265 attrs = {"name": projection.GetName()}
266 epsg = projection.EPSGCode()
267 if epsg is not None:
268 attrs["epsg"] = epsg
269 self.open_element("projection", attrs)
270 for param in projection.params:
271 self.write_element('parameter value="%s"' %
272 self.encode(param))
273 self.close_element("projection")
274
275 def write_layer(self, layer, attrs = None):
276 """Write the layer.
277
278 The optional argument attrs is for additional attributes and, if
279 given, should be a mapping from attribute names to attribute
280 values. The values should not be XML-escaped yet.
281 """
282
283 if attrs is None:
284 attrs = {}
285
286 attrs["title"] = layer.title
287 attrs["visible"] = ("false", "true")[int(layer.Visible())]
288
289 if isinstance(layer, Layer):
290 attrs["shapestore"] = self.get_id(layer.ShapeStore())
291
292 lc = layer.GetClassification()
293 attrs["stroke"] = lc.GetDefaultLineColor().hex()
294 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
295 attrs["fill"] = lc.GetDefaultFill().hex()
296
297 self.open_element("layer", attrs)
298 self.write_projection(layer.GetProjection())
299 self.write_classification(layer)
300 self.close_element("layer")
301 elif isinstance(layer, RasterLayer):
302 attrs["filename"] = self.prepare_filename(layer.filename)
303 self.open_element("rasterlayer", attrs)
304 self.write_projection(layer.GetProjection())
305 self.close_element("rasterlayer")
306
307 def write_classification(self, layer, attrs = None):
308 """Write Classification information."""
309
310 if attrs is None:
311 attrs = {}
312
313 lc = layer.GetClassification()
314
315 field = layer.GetClassificationColumn()
316
317 #
318 # there isn't a classification of anything so do nothing
319 #
320 if field is None: return
321
322 attrs["field"] = field
323 attrs["field_type"] = str(layer.GetFieldType(field))
324 self.open_element("classification", attrs)
325
326 for g in lc:
327 if isinstance(g, ClassGroupDefault):
328 open_el = 'clnull label="%s"' % self.encode(g.GetLabel())
329 close_el = 'clnull'
330 elif isinstance(g, ClassGroupSingleton):
331 if layer.GetFieldType(field) == FIELDTYPE_STRING:
332 value = self.encode(g.GetValue())
333 else:
334 value = str(g.GetValue())
335 open_el = 'clpoint label="%s" value="%s"' \
336 % (self.encode(g.GetLabel()), value)
337 close_el = 'clpoint'
338 elif isinstance(g, ClassGroupRange):
339 open_el = 'clrange label="%s" range="%s"' \
340 % (self.encode(g.GetLabel()), str(g.GetRange()))
341 close_el = 'clrange'
342 else:
343 assert False, _("Unsupported group type in classification")
344 continue
345
346 data = g.GetProperties()
347 dict = {'stroke' : data.GetLineColor().hex(),
348 'stroke_width': str(data.GetLineWidth()),
349 'fill' : data.GetFill().hex()}
350
351 # only for point layers write the size attribute
352 if layer.ShapeType() == SHAPETYPE_POINT:
353 dict['size'] = str(data.GetSize())
354
355 self.open_element(open_el)
356 self.write_element("cldata", dict)
357 self.close_element(close_el)
358
359 self.close_element("classification")
360
361 def write_label_layer(self, layer):
362 """Write the label layer.
363 """
364 labels = layer.Labels()
365 if labels:
366 self.open_element('labellayer')
367 for label in labels:
368 self.write_element(('label x="%g" y="%g" text="%s"'
369 ' halign="%s" valign="%s"')
370 % (label.x, label.y,
371 self.encode(label.text),
372 label.halign,
373 label.valign))
374 self.close_element('labellayer')
375
376
377
378 def save_session(session, file, saver_class = None):
379 """Save the session session to a file.
380
381 The file argument may either be a filename or an open file object.
382
383 The optional argument saver_class is the class to use to serialize
384 the session. By default or if it's None, the saver class will be
385 SessionSaver.
386
387 If writing the session is successful call the session's
388 UnsetModified method
389 """
390 if saver_class is None:
391 saver_class = SessionSaver
392 saver = saver_class(session)
393 saver.write(file)
394
395 # after a successful save consider the session unmodified.
396 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