/[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 2383 - (hide 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 jan 2383 # $Id$
2     # Copyright (c) 2001-2004 by Intevation GmbH
3 bh 6 # Authors:
4 jan 2383 # 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 bh 6 #
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 bh 201 import Thuban.Lib.fileutil
21 bh 6
22 jonathan 932 from Thuban.Model.layer import Layer, RasterLayer
23    
24 jonathan 876 from Thuban.Model.classification import \
25     ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
26 bh 1268 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
27 bh 1417 from Thuban.Model.table import DBFTable, FIELDTYPE_STRING
28 jan 2383 from Thuban.Model.data import DerivedShapeStore, ShapefileStore, \
29     SHAPETYPE_POINT
30 jonathan 429
31 jonathan 1160 from Thuban.Model.xmlwriter import XMLWriter
32 bh 1638 from postgisdb import PostGISConnection, PostGISShapeStore
33 jonathan 366
34 bh 201 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 bh 1268
47 bh 1989 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 bh 1268 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 jonathan 710 class SessionSaver(XMLWriter):
94 bh 268
95 jonathan 697 """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 jonathan 710 XMLWriter.__init__(self)
108 jonathan 697 self.session = session
109 bh 1268 # Map object ids to the ids used in the thuban files
110     self.idmap = {}
111 jonathan 697
112 bh 1268 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 bh 1989 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 jonathan 697 def write(self, file_or_filename):
135 jonathan 710 XMLWriter.write(self, file_or_filename)
136 jonathan 697
137 bh 2104 self.write_header("session", "thuban-1.1.dtd")
138 jonathan 697 self.write_session(self.session)
139     self.close()
140    
141 bh 268 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 bh 1268 # default name space
163     attrs["xmlns"] = \
164 bh 2104 "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"
165 jonathan 366 self.open_element("session", attrs)
166 bh 1638 self.write_db_connections(session)
167 bh 1268 self.write_data_containers(session)
168 bh 268 for map in session.Maps():
169     self.write_map(map)
170 jonathan 366 self.close_element("session")
171 bh 268
172 bh 1638 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 bh 1268 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 bh 1989 filename = self.prepare_filename(container.FileName())
203 bh 1268 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 bh 1638 elif isinstance(container, PostGISShapeStore):
213     conn = container.DBConnection()
214     self.write_element("dbshapesource",
215     {"id": idvalue,
216     "dbconn": self.get_id(conn),
217 bh 2104 "tablename": container.TableName(),
218     "id_column": container.IDColumn().name,
219     "geometry_column":
220     container.GeometryColumn().name,
221     })
222 bh 1268 elif isinstance(container, DBFTable):
223 bh 1989 filename = self.prepare_filename(container.FileName())
224 bh 1268 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 bh 1375 "leftcolumn": left_field,
240     "jointype": container.JoinType()})
241 bh 1268 else:
242     raise ValueError("Can't handle container %r" % container)
243    
244    
245 bh 268 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 jonathan 876 self.open_element('map title="%s"' % self.encode(map.title))
254 bh 268 self.write_projection(map.projection)
255     for layer in map.Layers():
256     self.write_layer(layer)
257     self.write_label_layer(map.LabelLayer())
258 jonathan 366 self.close_element('map')
259 bh 6
260 bh 268 def write_projection(self, projection):
261     """Write the projection.
262     """
263     if projection and len(projection.params) > 0:
264 bh 1843 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 bh 268 for param in projection.params:
270 jonathan 876 self.write_element('parameter value="%s"' %
271     self.encode(param))
272 jonathan 366 self.close_element("projection")
273 bh 268
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 jonathan 391
282 bh 268 if attrs is None:
283     attrs = {}
284 jonathan 429
285 jonathan 1251 attrs["title"] = layer.title
286     attrs["visible"] = ("false", "true")[int(layer.Visible())]
287 bh 268
288 jonathan 932 if isinstance(layer, Layer):
289 bh 1268 attrs["shapestore"] = self.get_id(layer.ShapeStore())
290 jonathan 740
291 jonathan 932 lc = layer.GetClassification()
292 bh 1268 attrs["stroke"] = lc.GetDefaultLineColor().hex()
293 jonathan 932 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
294 bh 1268 attrs["fill"] = lc.GetDefaultFill().hex()
295 jonathan 740
296 jonathan 932 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 bh 1989 attrs["filename"] = self.prepare_filename(layer.filename)
302 jonathan 932 self.open_element("rasterlayer", attrs)
303     self.write_projection(layer.GetProjection())
304     self.close_element("rasterlayer")
305    
306 jonathan 366 def write_classification(self, layer, attrs = None):
307 jonathan 1251 """Write Classification information."""
308    
309 jonathan 366 if attrs is None:
310     attrs = {}
311    
312 jonathan 414 lc = layer.GetClassification()
313 jonathan 366
314 bh 1452 field = layer.GetClassificationColumn()
315    
316 jonathan 366 #
317 jonathan 1251 # there isn't a classification of anything so do nothing
318 jonathan 366 #
319     if field is None: return
320    
321     attrs["field"] = field
322 jonathan 1430 attrs["field_type"] = str(layer.GetFieldType(field))
323 jonathan 366 self.open_element("classification", attrs)
324    
325 jonathan 1251 for g in lc:
326     if isinstance(g, ClassGroupDefault):
327     open_el = 'clnull label="%s"' % self.encode(g.GetLabel())
328     close_el = 'clnull'
329 bh 1417 elif isinstance(g, ClassGroupSingleton):
330 jonathan 1430 if layer.GetFieldType(field) == FIELDTYPE_STRING:
331 bh 1417 value = self.encode(g.GetValue())
332     else:
333     value = str(g.GetValue())
334 jonathan 1251 open_el = 'clpoint label="%s" value="%s"' \
335 bh 1417 % (self.encode(g.GetLabel()), value)
336 jonathan 1251 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 jonathan 429
345 jonathan 1251 data = g.GetProperties()
346     dict = {'stroke' : data.GetLineColor().hex(),
347     'stroke_width': str(data.GetLineWidth()),
348     'fill' : data.GetFill().hex()}
349 jonathan 429
350 jan 2383 # only for point layers write the size attribute
351     if layer.ShapeType() == SHAPETYPE_POINT:
352     dict['size'] = str(data.GetSize())
353    
354 jonathan 1251 self.open_element(open_el)
355     self.write_element("cldata", dict)
356     self.close_element(close_el)
357 jonathan 366
358     self.close_element("classification")
359    
360 bh 268 def write_label_layer(self, layer):
361     """Write the label layer.
362     """
363     labels = layer.Labels()
364 bh 6 if labels:
365 jonathan 366 self.open_element('labellayer')
366 bh 6 for label in labels:
367 jonathan 366 self.write_element(('label x="%g" y="%g" text="%s"'
368     ' halign="%s" valign="%s"')
369 jonathan 876 % (label.x, label.y,
370     self.encode(label.text),
371     label.halign,
372 bh 268 label.valign))
373 jonathan 366 self.close_element('labellayer')
374 bh 6
375 bh 268
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 jonathan 697 SessionSaver.
385 bh 268
386     If writing the session is successful call the session's
387     UnsetModified method
388     """
389     if saver_class is None:
390 jonathan 697 saver_class = SessionSaver
391 bh 268 saver = saver_class(session)
392     saver.write(file)
393    
394 bh 6 # 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