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