/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/load.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/Model/load.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1282 - (show annotations)
Mon Jun 23 09:47:18 2003 UTC (21 years, 8 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 17027 byte(s)
Bug fix for RT #1961:

* Thuban/Model/load.py (SessionLoader.start_derivedshapesource):
Register DerivedShapestores with the session

* Thuban/Model/session.py (Session.Tables): Make sure each table
is only listed once.

* test/test_load.py (TestJoinedTable.test): Add check_format call.
Update file contents to match the one written out.

1 # Copyright (C) 2001, 2002, 2003 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 GRASS for details.
9
10 """
11 Parser for thuban session files.
12 """
13
14 __version__ = "$Revision$"
15
16 import string, os
17
18 import xml.sax
19 import xml.sax.handler
20 from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
21
22 from Thuban import _
23
24 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
25 FIELDTYPE_STRING
26
27 from Thuban.Model.session import Session
28 from Thuban.Model.map import Map
29 from Thuban.Model.layer import Layer, RasterLayer
30 from Thuban.Model.color import Color
31 from Thuban.Model.proj import Projection
32 from Thuban.Model.range import Range
33 from Thuban.Model.classification import Classification, \
34 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
35 ClassGroupProperties
36 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
37 from Thuban.Model.table import DBFTable
38 from Thuban.Model.transientdb import TransientJoinedTable
39
40 class LoadError(Exception):
41 pass
42
43 from Thuban.Model.xmlreader import XMLReader
44 import resource
45
46 def parse_color(color):
47 """Return the color object for the string color.
48
49 Color may be either 'None' or of the form '#RRGGBB' in the usual
50 HTML color notation
51 """
52 color = string.strip(color)
53 if color == "None":
54 result = Color.Transparent
55 elif color[0] == '#':
56 if len(color) == 7:
57 r = string.atoi(color[1:3], 16) / 255.0
58 g = string.atoi(color[3:5], 16) / 255.0
59 b = string.atoi(color[5:7], 16) / 255.0
60 result = Color(r, g, b)
61 else:
62 raise ValueError(_("Invalid hexadecimal color specification %s")
63 % color)
64 else:
65 raise ValueError(_("Invalid color specification %s") % color)
66 return result
67
68 class AttrDesc:
69
70 def __init__(self, name, required = False, default = "",
71 conversion = None):
72 if not isinstance(name, tuple):
73 fullname = (None, name)
74 else:
75 fullname = name
76 name = name[1]
77 self.name = name
78 self.fullname = fullname
79 self.required = required
80 self.default = default
81 self.conversion = conversion
82
83 # set by the SessionLoader's check_attrs method
84 self.value = None
85
86
87 class SessionLoader(XMLReader):
88
89 def __init__(self):
90 """Inititialize the Sax handler."""
91 XMLReader.__init__(self)
92
93 self.theSession = None
94 self.aMap = None
95 self.aLayer = None
96
97 # Map ids used in the thuban file to the corresponding objects
98 # in the session
99 self.idmap = {}
100
101 dispatchers = {
102 'session' : ("start_session", "end_session"),
103 'fileshapesource': ("start_fileshapesource", None),
104 'derivedshapesource': ("start_derivedshapesource", None),
105 'filetable': ("start_filetable", None),
106 'jointable': ("start_jointable", None),
107
108 'map' : ("start_map", "end_map"),
109 'projection' : ("start_projection", "end_projection"),
110 'parameter' : ("start_parameter", None),
111 'layer' : ("start_layer", "end_layer"),
112 'rasterlayer' : ("start_rasterlayer", "end_rasterlayer"),
113 'classification': ("start_classification", "end_classification"),
114 'clnull' : ("start_clnull", "end_clnull"),
115 'clpoint' : ("start_clpoint", "end_clpoint"),
116 'clrange' : ("start_clrange", "end_clrange"),
117 'cldata' : ("start_cldata", "end_cldata"),
118 'table' : ("start_table", "end_table"),
119 'labellayer' : ("start_labellayer", None),
120 'label' : ("start_label", None)}
121
122 # all dispatchers should be used for the 0.8 namespace
123 xmlns = "http://thuban.intevation.org/dtds/thuban-0.8.dtd"
124 for key, value in dispatchers.items():
125 dispatchers[(xmlns, key)] = value
126
127 XMLReader.AddDispatchers(self, dispatchers)
128
129 def start_session(self, name, qname, attrs):
130 self.theSession = Session(self.encode(attrs.get((None, 'title'),
131 None)))
132
133 def end_session(self, name, qname):
134 pass
135
136 def check_attrs(self, element, attrs, descr):
137 """Check and convert some of the attributes of an element
138
139 Parameters:
140 element -- The element name
141 attrs -- The attrs mapping as passed to the start_* methods
142 descr -- Sequence of attribute descriptions (AttrDesc instances)
143
144 Return a dictionary containig normalized versions of the
145 attributes described in descr. The keys of that dictionary are
146 the name attributes of the attribute descriptions. The attrs
147 dictionary will not be modified.
148
149 If the attribute is required, i.e. the 'required' attribute of
150 the descrtiption is true, but it is not in attrs, raise a
151 LoadError.
152
153 If the attribute has a default value and it is not present in
154 attrs, use that default value as the value in the returned dict.
155
156 If a conversion is specified, convert the value before putting
157 it into the returned dict. The following conversions are
158 available:
159
160 'filename' -- The attribute is a filename.
161
162 If the filename is a relative name, interpret
163 it relative to the directory containing the
164 .thuban file and make it an absolute name
165
166 'shapestore' -- The attribute is the ID of a shapestore
167 defined earlier in the .thuban file. Look it
168 up self.idmap
169
170 'table' -- The attribute is the ID of a table or shapestore
171 defined earlier in the .thuban file. Look it up
172 self.idmap. If it's the ID of a shapestore the
173 value will be the table of the shapestore.
174 """
175 normalized = {}
176
177 for d in descr:
178 if d.required and not attrs.has_key(d.fullname):
179 pass
180 #raise LoadError("Element %s requires an attribute %r"
181 # % (element, d.name))
182 value = attrs.get(d.fullname, d.default)
183
184 if d.conversion == "shapesource":
185 if value in self.idmap:
186 value = self.idmap[value]
187 else:
188 raise LoadError("Element %s requires an already defined ID"
189 " in attribute %r"
190 % (element, d.name))
191 elif d.conversion == "table":
192 if value in self.idmap:
193 value = self.idmap[value]
194 if isinstance(value, ShapefileStore):
195 value = value.Table()
196 else:
197 raise LoadError("Element %s requires an already defined ID"
198 " in attribute %r"
199 % (element, d.name))
200 elif d.conversion == "filename":
201 value = os.path.abspath(os.path.join(self.GetDirectory(),
202 value))
203
204 normalized[d.name] = value
205 return normalized
206
207 def start_fileshapesource(self, name, qname, attrs):
208 attrs = self.check_attrs(name, attrs,
209 [AttrDesc("id", True),
210 AttrDesc("filename", True,
211 conversion = "filename"),
212 AttrDesc("filetype", True)])
213 ID = attrs["id"]
214 filename = attrs["filename"]
215 filetype = attrs["filetype"]
216 if filetype != "shapefile":
217 raise LoadError("shapesource filetype %r not supported" % filetype)
218 self.idmap[ID] = self.theSession.OpenShapefile(filename)
219
220 def start_derivedshapesource(self, name, qname, attrs):
221 attrs = self.check_attrs(name, attrs,
222 [AttrDesc("id", True),
223 AttrDesc("shapesource", True,
224 conversion = "shapesource"),
225 AttrDesc("table", True, conversion="table")])
226 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
227 self.theSession.AddShapeStore(store)
228 self.idmap[attrs["id"]] = store
229
230 def start_filetable(self, name, qname, attrs):
231 attrs = self.check_attrs(name, attrs,
232 [AttrDesc("id", True),
233 AttrDesc("title", True),
234 AttrDesc("filename", True,
235 conversion = "filename"),
236 AttrDesc("filetype")])
237 filetype = attrs["filetype"]
238 if filetype != "DBF":
239 raise LoadError("shapesource filetype %r not supported" % filetype)
240 table = DBFTable(attrs["filename"])
241 table.SetTitle(attrs["title"])
242 self.idmap[attrs["id"]] = self.theSession.AddTable(table)
243
244 def start_jointable(self, name, qname, attrs):
245 attrs = self.check_attrs(name, attrs,
246 [AttrDesc("id", True),
247 AttrDesc("title", True),
248 AttrDesc("left", True, conversion="table"),
249 AttrDesc("leftcolumn", True),
250 AttrDesc("right", True, conversion="table"),
251 AttrDesc("rightcolumn")])
252 table = TransientJoinedTable(self.theSession.TransientDB(),
253 attrs["left"], attrs["leftcolumn"],
254 attrs["right"], attrs["rightcolumn"])
255 table.SetTitle(attrs["title"])
256 self.idmap[attrs["id"]] = self.theSession.AddTable(table)
257
258 def start_map(self, name, qname, attrs):
259 """Start a map."""
260 self.aMap = Map(attrs.get((None, 'title'), None))
261
262 def end_map(self, name, qname):
263 self.theSession.AddMap(self.aMap)
264 self.aMap = None
265
266 def start_projection(self, name, qname, attrs):
267 self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
268 self.ProjectionParams = [ ]
269
270 def end_projection(self, name, qname):
271 if self.aLayer is not None:
272 obj = self.aLayer
273 elif self.aMap is not None:
274 obj = self.aMap
275 else:
276 assert False, "projection tag out of context"
277 pass
278
279 obj.SetProjection(
280 Projection(self.ProjectionParams, self.ProjectionName))
281
282 def start_parameter(self, name, qname, attrs):
283 s = attrs.get((None, 'value'))
284 s = str(s) # we can't handle unicode in proj
285 self.ProjectionParams.append(s)
286
287 def start_layer(self, name, qname, attrs, layer_class = Layer):
288 """Start a layer
289
290 Instantiate a layer of class layer_class from the attributes in
291 attrs which may be a dictionary as well as the normal SAX attrs
292 object and bind it to self.aLayer.
293 """
294 title = self.encode(attrs.get((None, 'title'), ""))
295 filename = attrs.get((None, 'filename'), "")
296 filename = os.path.join(self.GetDirectory(), filename)
297 filename = self.encode(filename)
298 visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
299 fill = parse_color(attrs.get((None, 'fill'), "None"))
300 stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
301 stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
302 if attrs.has_key((None, "shapestore")):
303 store = self.idmap[attrs[(None, "shapestore")]]
304 else:
305 store = self.theSession.OpenShapefile(filename)
306 self.aLayer = layer_class(title, store,
307 fill = fill, stroke = stroke,
308 lineWidth = stroke_width,
309 visible = visible)
310
311 def end_layer(self, name, qname):
312 self.aMap.AddLayer(self.aLayer)
313 self.aLayer = None
314
315 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
316 title = self.encode(attrs.get((None, 'title'), ""))
317 filename = attrs.get((None, 'filename'), "")
318 filename = os.path.join(self.GetDirectory(), filename)
319 filename = self.encode(filename)
320 visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
321
322 self.aLayer = layer_class(title, filename, visible = visible)
323
324 def end_rasterlayer(self, name, qname):
325 self.aMap.AddLayer(self.aLayer)
326 self.aLayer = None
327
328 def start_classification(self, name, qname, attrs):
329 field = attrs.get((None, 'field'), None)
330
331 fieldType = attrs.get((None, 'field_type'), None)
332 dbFieldType = self.aLayer.GetFieldType(field)
333
334 if fieldType != dbFieldType:
335 raise ValueError(_("xml field type differs from database!"))
336
337 # setup conversion routines depending on the kind of data
338 # we will be seeing later on
339 if fieldType == FIELDTYPE_STRING:
340 self.conv = str
341 elif fieldType == FIELDTYPE_INT:
342 self.conv = lambda p: int(float(p))
343 elif fieldType == FIELDTYPE_DOUBLE:
344 self.conv = float
345
346 self.aLayer.GetClassification().SetField(field)
347
348 def end_classification(self, name, qname):
349 pass
350
351 def start_clnull(self, name, qname, attrs):
352 self.cl_group = ClassGroupDefault()
353 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
354 self.cl_prop = ClassGroupProperties()
355
356 def end_clnull(self, name, qname):
357 self.cl_group.SetProperties(self.cl_prop)
358 self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
359 del self.cl_group, self.cl_prop
360
361 def start_clpoint(self, name, qname, attrs):
362 attrib_value = attrs.get((None, 'value'), "0")
363
364 value = self.conv(attrib_value)
365
366 self.cl_group = ClassGroupSingleton(value)
367 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
368 self.cl_prop = ClassGroupProperties()
369
370
371 def end_clpoint(self, name, qname):
372 self.cl_group.SetProperties(self.cl_prop)
373 self.aLayer.GetClassification().AppendGroup(self.cl_group)
374 del self.cl_group, self.cl_prop
375
376 def start_clrange(self, name, qname, attrs):
377
378 range = attrs.get((None, 'range'), None)
379 # for backward compatibility (min/max are not saved)
380 min = attrs.get((None, 'min'), None)
381 max = attrs.get((None, 'max'), None)
382
383 try:
384 if range is not None:
385 self.cl_group = ClassGroupRange(Range(range))
386 elif min is not None and max is not None:
387 self.cl_group = ClassGroupRange(self.conv(min), self.conv(max))
388 else:
389 self.cl_group = ClassGroupRange(Range(None))
390
391 except ValueError:
392 raise ValueError(_("Classification range is not a number!"))
393
394 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
395 self.cl_prop = ClassGroupProperties()
396
397
398 def end_clrange(self, name, qname):
399 self.cl_group.SetProperties(self.cl_prop)
400 self.aLayer.GetClassification().AppendGroup(self.cl_group)
401 del self.cl_group, self.cl_prop
402
403 def start_cldata(self, name, qname, attrs):
404 self.cl_prop.SetLineColor(
405 parse_color(attrs.get((None, 'stroke'), "None")))
406 self.cl_prop.SetLineWidth(
407 int(attrs.get((None, 'stroke_width'), "0")))
408 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
409
410 def end_cldata(self, name, qname):
411 pass
412
413 def start_labellayer(self, name, qname, attrs):
414 self.aLayer = self.aMap.LabelLayer()
415
416 def start_label(self, name, qname, attrs):
417 x = float(attrs[(None, 'x')])
418 y = float(attrs[(None, 'y')])
419 text = self.encode(attrs[(None, 'text')])
420 halign = attrs[(None, 'halign')]
421 valign = attrs[(None, 'valign')]
422 self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
423
424 def characters(self, chars):
425 pass
426
427
428 def load_session(filename):
429 """Load a Thuban session from the file object file"""
430
431 handler = SessionLoader()
432 handler.read(filename)
433
434 session = handler.theSession
435 # Newly loaded session aren't modified
436 session.UnsetModified()
437
438 return session
439

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26