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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1282 - (hide 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 bh 723 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4 bh 267 # Bernhard Herzog <[email protected]>
5 jonathan 413 # Jonathan Coles <[email protected]>
6 bh 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 bh 723 import string, os
17 bh 267
18     import xml.sax
19     import xml.sax.handler
20 jonathan 526 from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
21 bh 267
22 jan 374 from Thuban import _
23 jonathan 413
24 jonathan 473 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
25     FIELDTYPE_STRING
26    
27 bh 6 from Thuban.Model.session import Session
28     from Thuban.Model.map import Map
29 jonathan 930 from Thuban.Model.layer import Layer, RasterLayer
30 bh 6 from Thuban.Model.color import Color
31     from Thuban.Model.proj import Projection
32 jonathan 874 from Thuban.Model.range import Range
33 jonathan 413 from Thuban.Model.classification import Classification, \
34 jonathan 439 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
35     ClassGroupProperties
36 bh 1268 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
37     from Thuban.Model.table import DBFTable
38     from Thuban.Model.transientdb import TransientJoinedTable
39 bh 6
40 bh 1268 class LoadError(Exception):
41     pass
42    
43 jonathan 1159 from Thuban.Model.xmlreader import XMLReader
44     import resource
45 bh 6
46 bh 267 def parse_color(color):
47     """Return the color object for the string color.
48 bh 6
49 bh 267 Color may be either 'None' or of the form '#RRGGBB' in the usual
50     HTML color notation
51 bh 6 """
52     color = string.strip(color)
53     if color == "None":
54 jonathan 610 result = Color.Transparent
55 bh 6 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 jan 374 raise ValueError(_("Invalid hexadecimal color specification %s")
63 bh 6 % color)
64     else:
65 jan 374 raise ValueError(_("Invalid color specification %s") % color)
66 bh 6 return result
67    
68 bh 1268 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 jonathan 706 class SessionLoader(XMLReader):
88 jonathan 694
89 jonathan 706 def __init__(self):
90 jonathan 694 """Inititialize the Sax handler."""
91 jonathan 706 XMLReader.__init__(self)
92 jonathan 694
93     self.theSession = None
94     self.aMap = None
95     self.aLayer = None
96    
97 bh 1268 # Map ids used in the thuban file to the corresponding objects
98     # in the session
99     self.idmap = {}
100 jonathan 706
101 bh 1268 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 bh 267 def start_session(self, name, qname, attrs):
130 bh 1268 self.theSession = Session(self.encode(attrs.get((None, 'title'),
131     None)))
132 bh 6
133 bh 267 def end_session(self, name, qname):
134     pass
135 bh 6
136 bh 1268 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 bh 1282 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
227     self.theSession.AddShapeStore(store)
228     self.idmap[attrs["id"]] = store
229 bh 1268
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 bh 267 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 jonathan 874 self.aMap = None
265 bh 267
266     def start_projection(self, name, qname, attrs):
267 jonathan 874 self.ProjectionName = self.encode(attrs.get((None, 'name'), None))
268 bh 267 self.ProjectionParams = [ ]
269    
270     def end_projection(self, name, qname):
271 jonathan 874 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 jonathan 744 Projection(self.ProjectionParams, self.ProjectionName))
281 bh 267
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 jonathan 874 title = self.encode(attrs.get((None, 'title'), ""))
295 bh 267 filename = attrs.get((None, 'filename'), "")
296 jonathan 694 filename = os.path.join(self.GetDirectory(), filename)
297 jonathan 930 filename = self.encode(filename)
298     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
299 bh 267 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 bh 1268 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 bh 723 fill = fill, stroke = stroke,
308 jonathan 772 lineWidth = stroke_width,
309 jonathan 930 visible = visible)
310 bh 267
311     def end_layer(self, name, qname):
312     self.aMap.AddLayer(self.aLayer)
313 jonathan 874 self.aLayer = None
314 bh 267
315 jonathan 930 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 jonathan 365 def start_classification(self, name, qname, attrs):
329 jonathan 465 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 jonathan 365 def end_classification(self, name, qname):
349     pass
350    
351     def start_clnull(self, name, qname, attrs):
352 jonathan 439 self.cl_group = ClassGroupDefault()
353 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
354 jonathan 439 self.cl_prop = ClassGroupProperties()
355 jonathan 365
356     def end_clnull(self, name, qname):
357 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
358     self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
359     del self.cl_group, self.cl_prop
360 jonathan 365
361     def start_clpoint(self, name, qname, attrs):
362     attrib_value = attrs.get((None, 'value'), "0")
363    
364 jonathan 465 value = self.conv(attrib_value)
365    
366 jonathan 439 self.cl_group = ClassGroupSingleton(value)
367 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
368 jonathan 439 self.cl_prop = ClassGroupProperties()
369 jonathan 413
370 jonathan 365
371     def end_clpoint(self, name, qname):
372 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
373 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
374 jonathan 439 del self.cl_group, self.cl_prop
375 jonathan 365
376     def start_clrange(self, name, qname, attrs):
377    
378 jonathan 874 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 jonathan 365 try:
384 jonathan 874 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 jonathan 365 except ValueError:
392 jan 374 raise ValueError(_("Classification range is not a number!"))
393 jonathan 365
394 jonathan 439 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
395     self.cl_prop = ClassGroupProperties()
396 jonathan 413
397 jonathan 365
398     def end_clrange(self, name, qname):
399 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
400 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
401 jonathan 439 del self.cl_group, self.cl_prop
402 jonathan 365
403     def start_cldata(self, name, qname, attrs):
404 jonathan 465 self.cl_prop.SetLineColor(
405     parse_color(attrs.get((None, 'stroke'), "None")))
406     self.cl_prop.SetLineWidth(
407 jonathan 390 int(attrs.get((None, 'stroke_width'), "0")))
408 jonathan 439 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
409 jonathan 365
410     def end_cldata(self, name, qname):
411     pass
412    
413 bh 267 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 jonathan 874 text = self.encode(attrs[(None, 'text')])
420 bh 267 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 jonathan 694 def load_session(filename):
429     """Load a Thuban session from the file object file"""
430    
431 jonathan 706 handler = SessionLoader()
432     handler.read(filename)
433 jonathan 694
434 bh 6 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