/[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 2104 - (show annotations)
Fri Mar 12 12:19:15 2004 UTC (20 years, 11 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/save.py
File MIME type: text/x-python
File size: 15320 byte(s)
Final step for explicit id/geometry columns: Loading and saving

* Resources/XML/thuban-1.1.dtd: New.  Derived from thuban-1.0.dtd
with the following changes:
(dbshapesource): Two new attributes id_column and geometry_column

* Thuban/Model/save.py (SessionSaver.write): Use the new dtd
(SessionSaver.write_session): Use the new namespace
(SessionSaver.write_data_containers): Write the new dbshapesource
parameters

* Thuban/Model/load.py (SessionLoader.__init__): New namespace for
the new file format version
(SessionLoader.start_dbshapesource): Handle the new db parameters

* test/test_save.py: Update to the new dtd and namespace
(SaveSessionTest.test_save_postgis): Update the NonConnectionStore
mock object to provide a working IDColumn method.

* test/test_load_1_0.py: New.  Copy of the test_load.py before
today's changes but with the round-trip tests removed.

* test/test_load_0_9.py: Update doc-string.

* test/test_load.py: Update all .thuban files to the new dtd and
namespace.
(TestPostGISLayer.file_contents): Add the new dbshapesource
paramters

1 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 # Authors:
3 # Jan-Oliver Wagner <[email protected]>
4 # Bernhard Herzog <[email protected]>
5 # Jonathan Coles <[email protected]>
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 import Thuban.Lib.fileutil
19
20 from Thuban.Model.layer import Layer, RasterLayer
21
22 from Thuban.Model.classification import \
23 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap
24 from Thuban.Model.transientdb import AutoTransientTable, TransientJoinedTable
25 from Thuban.Model.table import DBFTable, FIELDTYPE_STRING
26 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
27
28 from Thuban.Model.xmlwriter import XMLWriter
29 from postgisdb import PostGISConnection, PostGISShapeStore
30
31 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
44 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 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 class SessionSaver(XMLWriter):
91
92 """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 XMLWriter.__init__(self)
105 self.session = session
106 # Map object ids to the ids used in the thuban files
107 self.idmap = {}
108
109 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 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 def write(self, file_or_filename):
132 XMLWriter.write(self, file_or_filename)
133
134 self.write_header("session", "thuban-1.1.dtd")
135 self.write_session(self.session)
136 self.close()
137
138 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 # default name space
160 attrs["xmlns"] = \
161 "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"
162 self.open_element("session", attrs)
163 self.write_db_connections(session)
164 self.write_data_containers(session)
165 for map in session.Maps():
166 self.write_map(map)
167 self.close_element("session")
168
169 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 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 filename = self.prepare_filename(container.FileName())
200 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 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 "id_column": container.IDColumn().name,
216 "geometry_column":
217 container.GeometryColumn().name,
218 })
219 elif isinstance(container, DBFTable):
220 filename = self.prepare_filename(container.FileName())
221 self.write_element("filetable",
222 {"id": idvalue,
223 "title": container.Title(),
224 "filename": filename,
225 "filetype": "DBF"})
226 elif isinstance(container, TransientJoinedTable):
227 left, right = container.Dependencies()
228 left_field = container.left_field
229 right_field = container.right_field
230 self.write_element("jointable",
231 {"id": idvalue,
232 "title": container.Title(),
233 "right": self.get_id(right),
234 "rightcolumn": right_field,
235 "left": self.get_id(left),
236 "leftcolumn": left_field,
237 "jointype": container.JoinType()})
238 else:
239 raise ValueError("Can't handle container %r" % container)
240
241
242 def write_map(self, map):
243 """Write the map and its contents.
244
245 By default, write a map element element with the title
246 attribute, call write_projection to write the projection
247 element, call write_layer for each layer contained in the map
248 and finally call write_label_layer to write the label layer.
249 """
250 self.open_element('map title="%s"' % self.encode(map.title))
251 self.write_projection(map.projection)
252 for layer in map.Layers():
253 self.write_layer(layer)
254 self.write_label_layer(map.LabelLayer())
255 self.close_element('map')
256
257 def write_projection(self, projection):
258 """Write the projection.
259 """
260 if projection and len(projection.params) > 0:
261 attrs = {"name": projection.GetName()}
262 epsg = projection.EPSGCode()
263 if epsg is not None:
264 attrs["epsg"] = epsg
265 self.open_element("projection", attrs)
266 for param in projection.params:
267 self.write_element('parameter value="%s"' %
268 self.encode(param))
269 self.close_element("projection")
270
271 def write_layer(self, layer, attrs = None):
272 """Write the layer.
273
274 The optional argument attrs is for additional attributes and, if
275 given, should be a mapping from attribute names to attribute
276 values. The values should not be XML-escaped yet.
277 """
278
279 if attrs is None:
280 attrs = {}
281
282 attrs["title"] = layer.title
283 attrs["visible"] = ("false", "true")[int(layer.Visible())]
284
285 if isinstance(layer, Layer):
286 attrs["shapestore"] = self.get_id(layer.ShapeStore())
287
288 lc = layer.GetClassification()
289 attrs["stroke"] = lc.GetDefaultLineColor().hex()
290 attrs["stroke_width"] = str(lc.GetDefaultLineWidth())
291 attrs["fill"] = lc.GetDefaultFill().hex()
292
293 self.open_element("layer", attrs)
294 self.write_projection(layer.GetProjection())
295 self.write_classification(layer)
296 self.close_element("layer")
297 elif isinstance(layer, RasterLayer):
298 attrs["filename"] = self.prepare_filename(layer.filename)
299 self.open_element("rasterlayer", attrs)
300 self.write_projection(layer.GetProjection())
301 self.close_element("rasterlayer")
302
303 def write_classification(self, layer, attrs = None):
304 """Write Classification information."""
305
306 if attrs is None:
307 attrs = {}
308
309 lc = layer.GetClassification()
310
311 field = layer.GetClassificationColumn()
312
313 #
314 # there isn't a classification of anything so do nothing
315 #
316 if field is None: return
317
318 attrs["field"] = field
319 attrs["field_type"] = str(layer.GetFieldType(field))
320 self.open_element("classification", attrs)
321
322 for g in lc:
323 if isinstance(g, ClassGroupDefault):
324 open_el = 'clnull label="%s"' % self.encode(g.GetLabel())
325 close_el = 'clnull'
326 elif isinstance(g, ClassGroupSingleton):
327 if layer.GetFieldType(field) == FIELDTYPE_STRING:
328 value = self.encode(g.GetValue())
329 else:
330 value = str(g.GetValue())
331 open_el = 'clpoint label="%s" value="%s"' \
332 % (self.encode(g.GetLabel()), value)
333 close_el = 'clpoint'
334 elif isinstance(g, ClassGroupRange):
335 open_el = 'clrange label="%s" range="%s"' \
336 % (self.encode(g.GetLabel()), str(g.GetRange()))
337 close_el = 'clrange'
338 else:
339 assert False, _("Unsupported group type in classification")
340 continue
341
342 data = g.GetProperties()
343 dict = {'stroke' : data.GetLineColor().hex(),
344 'stroke_width': str(data.GetLineWidth()),
345 'fill' : data.GetFill().hex()}
346
347 self.open_element(open_el)
348 self.write_element("cldata", dict)
349 self.close_element(close_el)
350
351 self.close_element("classification")
352
353 def write_label_layer(self, layer):
354 """Write the label layer.
355 """
356 labels = layer.Labels()
357 if labels:
358 self.open_element('labellayer')
359 for label in labels:
360 self.write_element(('label x="%g" y="%g" text="%s"'
361 ' halign="%s" valign="%s"')
362 % (label.x, label.y,
363 self.encode(label.text),
364 label.halign,
365 label.valign))
366 self.close_element('labellayer')
367
368
369
370 def save_session(session, file, saver_class = None):
371 """Save the session session to a file.
372
373 The file argument may either be a filename or an open file object.
374
375 The optional argument saver_class is the class to use to serialize
376 the session. By default or if it's None, the saver class will be
377 SessionSaver.
378
379 If writing the session is successful call the session's
380 UnsetModified method
381 """
382 if saver_class is None:
383 saver_class = SessionSaver
384 saver = saver_class(session)
385 saver.write(file)
386
387 # after a successful save consider the session unmodified.
388 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