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