/[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 1339 - (show annotations)
Tue Jul 1 16:10:14 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 17050 byte(s)
Fixes RTbug #1971.
(SessionLoader.start_classification): Call
        Classification.SetFieldInfo().

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26