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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2616 - (hide 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 jan 2606 # Copyright (c) 2001-2005 by Intevation GmbH
2 bh 6 # Authors:
3 jan 2383 # 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 bh 6 #
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 jan 2384 # $Source$
17     # $Id$
18 bh 6
19     import os
20    
21 bh 201 import Thuban.Lib.fileutil
22 bh 6
23 jonathan 932 from Thuban.Model.layer import Layer, RasterLayer
24    
25 jonathan 876 from Thuban.Model.classification import \
26     ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
27 bh 1268 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
28 bh 1417 from Thuban.Model.table import DBFTable, FIELDTYPE_STRING
29 jan 2606 from Thuban.Model.data import DerivedShapeStore, FileShapeStore, \
30 jan 2383 SHAPETYPE_POINT
31 jonathan 429
32 jonathan 1160 from Thuban.Model.xmlwriter import XMLWriter
33 bh 1638 from postgisdb import PostGISConnection, PostGISShapeStore
34 jonathan 366
35 bh 201 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 bh 1268
48 bh 1989 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 bh 1268 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 jonathan 2551 def bool2str(b):
94     if b: return "true"
95     else: return "false"
96 bh 1268
97 jonathan 710 class SessionSaver(XMLWriter):
98 bh 268
99 jonathan 697 """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 jonathan 710 XMLWriter.__init__(self)
112 jonathan 697 self.session = session
113 bh 1268 # Map object ids to the ids used in the thuban files
114     self.idmap = {}
115 jonathan 697
116 bh 1268 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 bh 1989 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 jonathan 697 def write(self, file_or_filename):
139 jonathan 710 XMLWriter.write(self, file_or_filename)
140 jonathan 697
141 bh 2104 self.write_header("session", "thuban-1.1.dtd")
142 jonathan 697 self.write_session(self.session)
143     self.close()
144    
145 bh 268 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 bh 1268 # default name space
167     attrs["xmlns"] = \
168 bh 2104 "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"
169 jonathan 366 self.open_element("session", attrs)
170 bh 1638 self.write_db_connections(session)
171 bh 1268 self.write_data_containers(session)
172 bh 268 for map in session.Maps():
173     self.write_map(map)
174 jonathan 366 self.close_element("session")
175 bh 268
176 bh 1638 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 bh 1268 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 jan 2606 if isinstance(container, FileShapeStore):
205 bh 1268 self.define_id(container.Table(), idvalue)
206 bh 1989 filename = self.prepare_filename(container.FileName())
207 bh 1268 self.write_element("fileshapesource",
208     {"id": idvalue, "filename": filename,
209 jan 2606 "filetype": container.FileType()})
210 bh 1268 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 bh 1638 elif isinstance(container, PostGISShapeStore):
217     conn = container.DBConnection()
218     self.write_element("dbshapesource",
219     {"id": idvalue,
220     "dbconn": self.get_id(conn),
221 bh 2104 "tablename": container.TableName(),
222     "id_column": container.IDColumn().name,
223     "geometry_column":
224     container.GeometryColumn().name,
225     })
226 bh 1268 elif isinstance(container, DBFTable):
227 bh 1989 filename = self.prepare_filename(container.FileName())
228 bh 1268 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 bh 1375 "leftcolumn": left_field,
244     "jointype": container.JoinType()})
245 bh 1268 else:
246     raise ValueError("Can't handle container %r" % container)
247    
248    
249 bh 268 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 jonathan 876 self.open_element('map title="%s"' % self.encode(map.title))
258 bh 268 self.write_projection(map.projection)
259     for layer in map.Layers():
260     self.write_layer(layer)
261     self.write_label_layer(map.LabelLayer())
262 jonathan 366 self.close_element('map')
263 bh 6
264 bh 268 def write_projection(self, projection):
265     """Write the projection.
266     """
267     if projection and len(projection.params) > 0:
268 bh 1843 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 bh 268 for param in projection.params:
274 jonathan 876 self.write_element('parameter value="%s"' %
275     self.encode(param))
276 jonathan 366 self.close_element("projection")
277 bh 268
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 jonathan 391
286 bh 268 if attrs is None:
287     attrs = {}
288 jonathan 429
289 jonathan 1251 attrs["title"] = layer.title
290 jonathan 2551 attrs["visible"] = bool2str(layer.Visible())
291 bh 268
292 jonathan 932 if isinstance(layer, Layer):
293 bh 1268 attrs["shapestore"] = self.get_id(layer.ShapeStore())
294 jonathan 740
295 jonathan 932 lc = layer.GetClassification()
296 bh 1268 attrs["stroke"] = lc.GetDefaultLineColor().hex()
297 jonathan 932 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
298 bh 1268 attrs["fill"] = lc.GetDefaultFill().hex()
299 jonathan 740
300 jonathan 932 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 bh 1989 attrs["filename"] = self.prepare_filename(layer.filename)
306 jonathan 2616
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 jonathan 932 self.open_element("rasterlayer", attrs)
316     self.write_projection(layer.GetProjection())
317     self.close_element("rasterlayer")
318    
319 jonathan 366 def write_classification(self, layer, attrs = None):
320 jonathan 1251 """Write Classification information."""
321    
322 jonathan 366 if attrs is None:
323     attrs = {}
324    
325 jonathan 414 lc = layer.GetClassification()
326 jonathan 366
327 bh 1452 field = layer.GetClassificationColumn()
328    
329 jonathan 366 #
330 jonathan 1251 # there isn't a classification of anything so do nothing
331 jonathan 366 #
332     if field is None: return
333    
334     attrs["field"] = field
335 jonathan 1430 attrs["field_type"] = str(layer.GetFieldType(field))
336 jonathan 366 self.open_element("classification", attrs)
337    
338 jonathan 1251 for g in lc:
339     if isinstance(g, ClassGroupDefault):
340     open_el = 'clnull label="%s"' % self.encode(g.GetLabel())
341     close_el = 'clnull'
342 bh 1417 elif isinstance(g, ClassGroupSingleton):
343 jonathan 1430 if layer.GetFieldType(field) == FIELDTYPE_STRING:
344 bh 1417 value = self.encode(g.GetValue())
345     else:
346     value = str(g.GetValue())
347 jonathan 1251 open_el = 'clpoint label="%s" value="%s"' \
348 bh 1417 % (self.encode(g.GetLabel()), value)
349 jonathan 1251 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 jonathan 429
358 jonathan 1251 data = g.GetProperties()
359     dict = {'stroke' : data.GetLineColor().hex(),
360     'stroke_width': str(data.GetLineWidth()),
361     'fill' : data.GetFill().hex()}
362 jonathan 429
363 jan 2383 # only for point layers write the size attribute
364     if layer.ShapeType() == SHAPETYPE_POINT:
365     dict['size'] = str(data.GetSize())
366    
367 jonathan 1251 self.open_element(open_el)
368     self.write_element("cldata", dict)
369     self.close_element(close_el)
370 jonathan 366
371     self.close_element("classification")
372    
373 bh 268 def write_label_layer(self, layer):
374     """Write the label layer.
375     """
376     labels = layer.Labels()
377 bh 6 if labels:
378 jonathan 366 self.open_element('labellayer')
379 bh 6 for label in labels:
380 jonathan 366 self.write_element(('label x="%g" y="%g" text="%s"'
381     ' halign="%s" valign="%s"')
382 jonathan 876 % (label.x, label.y,
383     self.encode(label.text),
384     label.halign,
385 bh 268 label.valign))
386 jonathan 366 self.close_element('labellayer')
387 bh 6
388 bh 268
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 jonathan 697 SessionSaver.
398 bh 268
399     If writing the session is successful call the session's
400     UnsetModified method
401     """
402     if saver_class is None:
403 jonathan 697 saver_class = SessionSaver
404 bh 268 saver = saver_class(session)
405     saver.write(file)
406    
407 bh 6 # 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