/[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 1428 - (show annotations)
Wed Jul 16 13:23:01 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 18236 byte(s)
(SessionLoader.start_classification):
        Set the field name on the layer, not the classification.

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 and 0.9 namespaces too
124 for xmlns in ("http://thuban.intevation.org/dtds/thuban-0.8.dtd",
125 "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd"):
126 for key, value in dispatchers.items():
127 dispatchers[(xmlns, key)] = value
128
129 XMLReader.AddDispatchers(self, dispatchers)
130
131 def start_session(self, name, qname, attrs):
132 self.theSession = Session(self.encode(attrs.get((None, 'title'),
133 None)))
134
135 def end_session(self, name, qname):
136 pass
137
138 def check_attrs(self, element, attrs, descr):
139 """Check and convert some of the attributes of an element
140
141 Parameters:
142 element -- The element name
143 attrs -- The attrs mapping as passed to the start_* methods
144 descr -- Sequence of attribute descriptions (AttrDesc instances)
145
146 Return a dictionary containig normalized versions of the
147 attributes described in descr. The keys of that dictionary are
148 the name attributes of the attribute descriptions. The attrs
149 dictionary will not be modified.
150
151 If the attribute is required, i.e. the 'required' attribute of
152 the descrtiption is true, but it is not in attrs, raise a
153 LoadError.
154
155 If the attribute has a default value and it is not present in
156 attrs, use that default value as the value in the returned dict.
157
158 If a conversion is specified, convert the value before putting
159 it into the returned dict. The following conversions are
160 available:
161
162 'filename' -- The attribute is a filename.
163
164 If the filename is a relative name, interpret
165 it relative to the directory containing the
166 .thuban file and make it an absolute name
167
168 'shapestore' -- The attribute is the ID of a shapestore
169 defined earlier in the .thuban file. Look it
170 up self.idmap
171
172 'table' -- The attribute is the ID of a table or shapestore
173 defined earlier in the .thuban file. Look it up
174 self.idmap. If it's the ID of a shapestore the
175 value will be the table of the shapestore.
176 """
177 normalized = {}
178
179 for d in descr:
180 if d.required and not attrs.has_key(d.fullname):
181 pass
182 #raise LoadError("Element %s requires an attribute %r"
183 # % (element, d.name))
184 value = attrs.get(d.fullname, d.default)
185
186 if d.conversion == "shapesource":
187 if value in self.idmap:
188 value = self.idmap[value]
189 else:
190 raise LoadError("Element %s requires an already defined ID"
191 " in attribute %r"
192 % (element, d.name))
193 elif d.conversion == "table":
194 if value in self.idmap:
195 value = self.idmap[value]
196 if isinstance(value, ShapefileStore):
197 value = value.Table()
198 else:
199 raise LoadError("Element %s requires an already defined ID"
200 " in attribute %r"
201 % (element, d.name))
202 elif d.conversion == "filename":
203 value = os.path.abspath(os.path.join(self.GetDirectory(),
204 value))
205
206 normalized[d.name] = value
207 return normalized
208
209 def start_fileshapesource(self, name, qname, attrs):
210 attrs = self.check_attrs(name, attrs,
211 [AttrDesc("id", True),
212 AttrDesc("filename", True,
213 conversion = "filename"),
214 AttrDesc("filetype", True)])
215 ID = attrs["id"]
216 filename = attrs["filename"]
217 filetype = attrs["filetype"]
218 if filetype != "shapefile":
219 raise LoadError("shapesource filetype %r not supported" % filetype)
220 self.idmap[ID] = self.theSession.OpenShapefile(filename)
221
222 def start_derivedshapesource(self, name, qname, attrs):
223 attrs = self.check_attrs(name, attrs,
224 [AttrDesc("id", True),
225 AttrDesc("shapesource", True,
226 conversion = "shapesource"),
227 AttrDesc("table", True, conversion="table")])
228 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
229 self.theSession.AddShapeStore(store)
230 self.idmap[attrs["id"]] = store
231
232 def start_filetable(self, name, qname, attrs):
233 attrs = self.check_attrs(name, attrs,
234 [AttrDesc("id", True),
235 AttrDesc("title", True),
236 AttrDesc("filename", True,
237 conversion = "filename"),
238 AttrDesc("filetype")])
239 filetype = attrs["filetype"]
240 if filetype != "DBF":
241 raise LoadError("shapesource filetype %r not supported" % filetype)
242 table = DBFTable(attrs["filename"])
243 table.SetTitle(attrs["title"])
244 self.idmap[attrs["id"]] = self.theSession.AddTable(table)
245
246 def start_jointable(self, name, qname, attrs):
247 attrs = self.check_attrs(name, attrs,
248 [AttrDesc("id", True),
249 AttrDesc("title", True),
250 AttrDesc("left", True, conversion="table"),
251 AttrDesc("leftcolumn", True),
252 AttrDesc("right", True, conversion="table"),
253 AttrDesc("rightcolumn", True),
254
255 # jointype is required for file
256 # version 0.9 but this attribute
257 # wasn't in the 0.8 version because of
258 # an oversight so we assume it's
259 # optional since we want to handle
260 # both file format versions here.
261 AttrDesc("jointype", False,
262 default="INNER")])
263
264 jointype = attrs["jointype"]
265 if jointype == "LEFT OUTER":
266 outer_join = True
267 elif jointype == "INNER":
268 outer_join = False
269 else:
270 raise LoadError("jointype %r not supported" % jointype )
271 table = TransientJoinedTable(self.theSession.TransientDB(),
272 attrs["left"], attrs["leftcolumn"],
273 attrs["right"], attrs["rightcolumn"],
274 outer_join = outer_join)
275 table.SetTitle(attrs["title"])
276 self.idmap[attrs["id"]] = self.theSession.AddTable(table)
277
278 def start_map(self, name, qname, attrs):
279 """Start a map."""
280 self.aMap = Map(self.encode(attrs.get((None, 'title'), None)))
281
282 def end_map(self, name, qname):
283 self.theSession.AddMap(self.aMap)
284 self.aMap = None
285
286 def start_projection(self, name, qname, attrs):
287 self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
288 self.ProjectionParams = [ ]
289
290 def end_projection(self, name, qname):
291 if self.aLayer is not None:
292 obj = self.aLayer
293 elif self.aMap is not None:
294 obj = self.aMap
295 else:
296 assert False, "projection tag out of context"
297 pass
298
299 obj.SetProjection(
300 Projection(self.ProjectionParams, self.ProjectionName))
301
302 def start_parameter(self, name, qname, attrs):
303 s = attrs.get((None, 'value'))
304 s = str(s) # we can't handle unicode in proj
305 self.ProjectionParams.append(s)
306
307 def start_layer(self, name, qname, attrs, layer_class = Layer):
308 """Start a layer
309
310 Instantiate a layer of class layer_class from the attributes in
311 attrs which may be a dictionary as well as the normal SAX attrs
312 object and bind it to self.aLayer.
313 """
314 title = self.encode(attrs.get((None, 'title'), ""))
315 filename = attrs.get((None, 'filename'), "")
316 filename = os.path.join(self.GetDirectory(), filename)
317 filename = self.encode(filename)
318 visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
319 fill = parse_color(attrs.get((None, 'fill'), "None"))
320 stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
321 stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
322 if attrs.has_key((None, "shapestore")):
323 store = self.idmap[attrs[(None, "shapestore")]]
324 else:
325 store = self.theSession.OpenShapefile(filename)
326 self.aLayer = layer_class(title, store,
327 fill = fill, stroke = stroke,
328 lineWidth = stroke_width,
329 visible = visible)
330
331 def end_layer(self, name, qname):
332 self.aMap.AddLayer(self.aLayer)
333 self.aLayer = None
334
335 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
336 title = self.encode(attrs.get((None, 'title'), ""))
337 filename = attrs.get((None, 'filename'), "")
338 filename = os.path.join(self.GetDirectory(), filename)
339 filename = self.encode(filename)
340 visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
341
342 self.aLayer = layer_class(title, filename, visible = visible)
343
344 def end_rasterlayer(self, name, qname):
345 self.aMap.AddLayer(self.aLayer)
346 self.aLayer = None
347
348 def start_classification(self, name, qname, attrs):
349 field = attrs.get((None, 'field'), None)
350
351 fieldType = attrs.get((None, 'field_type'), None)
352 dbFieldType = self.aLayer.GetFieldType(field)
353
354 if fieldType != dbFieldType:
355 raise ValueError(_("xml field type differs from database!"))
356
357 # setup conversion routines depending on the kind of data
358 # we will be seeing later on
359 if fieldType == FIELDTYPE_STRING:
360 self.conv = str
361 elif fieldType == FIELDTYPE_INT:
362 self.conv = lambda p: int(float(p))
363 elif fieldType == FIELDTYPE_DOUBLE:
364 self.conv = float
365
366 self.aLayer.SetClassificationField(field)
367
368 def end_classification(self, name, qname):
369 pass
370
371 def start_clnull(self, name, qname, attrs):
372 self.cl_group = ClassGroupDefault()
373 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
374 self.cl_prop = ClassGroupProperties()
375
376 def end_clnull(self, name, qname):
377 self.cl_group.SetProperties(self.cl_prop)
378 self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
379 del self.cl_group, self.cl_prop
380
381 def start_clpoint(self, name, qname, attrs):
382 attrib_value = attrs.get((None, 'value'), "0")
383
384 field = self.aLayer.GetClassificationField()
385 if self.aLayer.GetFieldType(field) == FIELDTYPE_STRING:
386 value = self.encode(attrib_value)
387 else:
388 value = self.conv(attrib_value)
389 self.cl_group = ClassGroupSingleton(value)
390 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
391 self.cl_prop = ClassGroupProperties()
392
393
394 def end_clpoint(self, name, qname):
395 self.cl_group.SetProperties(self.cl_prop)
396 self.aLayer.GetClassification().AppendGroup(self.cl_group)
397 del self.cl_group, self.cl_prop
398
399 def start_clrange(self, name, qname, attrs):
400
401 range = attrs.get((None, 'range'), None)
402 # for backward compatibility (min/max are not saved)
403 min = attrs.get((None, 'min'), None)
404 max = attrs.get((None, 'max'), None)
405
406 try:
407 if range is not None:
408 self.cl_group = ClassGroupRange(Range(range))
409 elif min is not None and max is not None:
410 self.cl_group = ClassGroupRange((self.conv(min),
411 self.conv(max)))
412 else:
413 self.cl_group = ClassGroupRange(Range(None))
414
415 except ValueError:
416 raise ValueError(_("Classification range is not a number!"))
417
418 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
419 self.cl_prop = ClassGroupProperties()
420
421
422 def end_clrange(self, name, qname):
423 self.cl_group.SetProperties(self.cl_prop)
424 self.aLayer.GetClassification().AppendGroup(self.cl_group)
425 del self.cl_group, self.cl_prop
426
427 def start_cldata(self, name, qname, attrs):
428 self.cl_prop.SetLineColor(
429 parse_color(attrs.get((None, 'stroke'), "None")))
430 self.cl_prop.SetLineWidth(
431 int(attrs.get((None, 'stroke_width'), "0")))
432 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
433
434 def end_cldata(self, name, qname):
435 pass
436
437 def start_labellayer(self, name, qname, attrs):
438 self.aLayer = self.aMap.LabelLayer()
439
440 def start_label(self, name, qname, attrs):
441 x = float(attrs[(None, 'x')])
442 y = float(attrs[(None, 'y')])
443 text = self.encode(attrs[(None, 'text')])
444 halign = attrs[(None, 'halign')]
445 valign = attrs[(None, 'valign')]
446 self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
447
448 def characters(self, chars):
449 pass
450
451
452 def load_session(filename):
453 """Load a Thuban session from the file object file"""
454
455 handler = SessionLoader()
456 handler.read(filename)
457
458 session = handler.theSession
459 # Newly loaded session aren't modified
460 session.UnsetModified()
461
462 return session
463

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26