/[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 2036 - (hide annotations)
Mon Dec 22 17:49:43 2003 UTC (21 years, 2 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 15072 byte(s)
* setup.py (setup call): 1.0.0, yeah!

* Thuban/version.py (longversion): 1.0.0, yeah!

* Thuban/Model/load.py (SessionLoader.__init__): Accept the
1.0.0 namespace too

* Thuban/Model/save.py (SessionSaver.write_session): Save with
1.0.0 namespace

* test/test_load.py (LoadSessionTest.dtd)
(TestSingleLayer.file_contents)
(TestNonAsciiColumnName.file_contents)
(TestLayerVisibility.file_contents)
(TestClassification.file_contents, TestLabels.file_contents)
(TestLayerProjection.file_contents)
(TestRasterLayer.file_contents, TestJoinedTable.file_contents)
(TestLabelLayer.file_contents, TestPostGISLayer.file_contents)
(TestPostGISLayerPassword.file_contents)
(TestLoadError.file_contents, TestLoadError.test): Update for
1.0.0 namespace

* test/test_save.py (SaveSessionTest.dtd)
(SaveSessionTest.testEmptySession)
(SaveSessionTest.testSingleLayer)
(SaveSessionTest.testLayerProjection)
(SaveSessionTest.testRasterLayer)
(SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.test_dbf_table)
(SaveSessionTest.test_joined_table)
(SaveSessionTest.test_save_postgis): Update for 1.0.0 namespace

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