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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

trunk/thuban/Thuban/Model/load.py revision 1452 by bh, Fri Jul 18 12:57:59 2003 UTC branches/WIP-pyshapelib-bramz/Thuban/Model/load.py revision 2734 by bramz, Thu Mar 1 12:42:59 2007 UTC
# Line 1  Line 1 
1  # Copyright (C) 2001, 2002, 2003 by Intevation GmbH  # Copyright (C) 2001, 2002, 2003, 2004, 2005 by Intevation GmbH
2  # Authors:  # Authors:
3  # Jan-Oliver Wagner <[email protected]>  # Jan-Oliver Wagner <[email protected]>
4  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
5  # Jonathan Coles <[email protected]>  # Jonathan Coles <[email protected]>
6    # Frank Koormann <[email protected]>
7  #  #
8  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
9  # Read the file COPYING coming with GRASS for details.  # Read the file COPYING coming with GRASS for details.
# Line 32  from Thuban.Model.layer import Layer, Ra Line 33  from Thuban.Model.layer import Layer, Ra
33  from Thuban.Model.proj import Projection  from Thuban.Model.proj import Projection
34  from Thuban.Model.range import Range  from Thuban.Model.range import Range
35  from Thuban.Model.classification import Classification, \  from Thuban.Model.classification import Classification, \
36      ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \      ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, \
37        ClassGroupPattern, ClassGroupMap, \
38      ClassGroupProperties      ClassGroupProperties
39  from Thuban.Model.data import DerivedShapeStore, ShapefileStore  from Thuban.Model.data import DerivedShapeStore, ShapefileStore
40  from Thuban.Model.table import DBFTable  from Thuban.Model.table import DBFTable
41  from Thuban.Model.transientdb import TransientJoinedTable  from Thuban.Model.transientdb import TransientJoinedTable
42    
43    from Thuban.Model.xmlreader import XMLReader
44    import resource
45    
46    import postgisdb
47    
48  class LoadError(Exception):  class LoadError(Exception):
     pass  
49    
50  from Thuban.Model.xmlreader import XMLReader      """Exception raised when the thuban file is corrupted
51  import resource  
52        Not all cases of corrupted thuban files will lead to this exception
53        but those that are found by checks in the loading code itself are.
54        """
55    
56    
57    class LoadCancelled(Exception):
58    
59        """Exception raised to indicate that loading was interrupted by the user"""
60    
61    
62  def parse_color(color):  def parse_color(color):
63      """Return the color object for the string color.      """Return the color object for the string color.
# Line 87  class AttrDesc: Line 102  class AttrDesc:
102    
103  class SessionLoader(XMLReader):  class SessionLoader(XMLReader):
104    
105      def __init__(self):      def __init__(self, db_connection_callback = None,
106                           shapefile_callback = None):
107          """Inititialize the Sax handler."""          """Inititialize the Sax handler."""
108          XMLReader.__init__(self)          XMLReader.__init__(self)
109    
110            self.db_connection_callback = db_connection_callback
111            self.shapefile_callback = shapefile_callback
112          self.theSession = None          self.theSession = None
113          self.aMap = None          self.aMap = None
114          self.aLayer = None          self.aLayer = None
# Line 101  class SessionLoader(XMLReader): Line 119  class SessionLoader(XMLReader):
119    
120          dispatchers = {          dispatchers = {
121              'session'       : ("start_session",        "end_session"),              'session'       : ("start_session",        "end_session"),
122    
123                'dbconnection': ("start_dbconnection", None),
124    
125                'dbshapesource': ("start_dbshapesource", None),
126              'fileshapesource': ("start_fileshapesource", None),              'fileshapesource': ("start_fileshapesource", None),
127              'derivedshapesource': ("start_derivedshapesource", None),              'derivedshapesource': ("start_derivedshapesource", None),
128              'filetable': ("start_filetable", None),              'filetable': ("start_filetable", None),
# Line 115  class SessionLoader(XMLReader): Line 137  class SessionLoader(XMLReader):
137              'clnull'        : ("start_clnull",         "end_clnull"),              'clnull'        : ("start_clnull",         "end_clnull"),
138              'clpoint'       : ("start_clpoint",        "end_clpoint"),              'clpoint'       : ("start_clpoint",        "end_clpoint"),
139              'clrange'       : ("start_clrange",        "end_clrange"),              'clrange'       : ("start_clrange",        "end_clrange"),
140                'clpattern'     : ("start_clpattern",      "end_clpattern"),
141              'cldata'        : ("start_cldata",         "end_cldata"),              'cldata'        : ("start_cldata",         "end_cldata"),
142              'table'         : ("start_table",          "end_table"),              'table'         : ("start_table",          "end_table"),
143              'labellayer'    : ("start_labellayer",     None),              'labellayer'    : ("start_labellayer",     None),
# Line 122  class SessionLoader(XMLReader): Line 145  class SessionLoader(XMLReader):
145    
146          # all dispatchers should be used for the 0.8 and 0.9 namespaces too          # all dispatchers should be used for the 0.8 and 0.9 namespaces too
147          for xmlns in ("http://thuban.intevation.org/dtds/thuban-0.8.dtd",          for xmlns in ("http://thuban.intevation.org/dtds/thuban-0.8.dtd",
148                        "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd"):                        "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd",
149                          "http://thuban.intevation.org/dtds/thuban-0.9.dtd",
150                          "http://thuban.intevation.org/dtds/thuban-1.0-dev.dtd",
151                          "http://thuban.intevation.org/dtds/thuban-1.0rc1.dtd",
152                          "http://thuban.intevation.org/dtds/thuban-1.0.0.dtd",
153                          "http://thuban.intevation.org/dtds/thuban-1.1-dev.dtd"):
154              for key, value in dispatchers.items():              for key, value in dispatchers.items():
155                  dispatchers[(xmlns, key)] = value                  dispatchers[(xmlns, key)] = value
156    
157          XMLReader.AddDispatchers(self, dispatchers)          XMLReader.AddDispatchers(self, dispatchers)
158    
159        def Destroy(self):
160            """Clear all instance variables to cut cyclic references.
161    
162            The GC would have collected the loader eventually but it can
163            happen that it doesn't run at all until Thuban is closed (2.3
164            but not 2.2 tries a bit harder and forces a collection when the
165            interpreter terminates)
166            """
167            self.__dict__.clear()
168    
169      def start_session(self, name, qname, attrs):      def start_session(self, name, qname, attrs):
170          self.theSession = Session(self.encode(attrs.get((None, 'title'),          self.theSession = Session(self.encode(attrs.get((None, 'title'),
171                                                          None)))                                                          None)))
# Line 155  class SessionLoader(XMLReader): Line 193  class SessionLoader(XMLReader):
193          If the attribute has a default value and it is not present in          If the attribute has a default value and it is not present in
194          attrs, use that default value as the value in the returned dict.          attrs, use that default value as the value in the returned dict.
195    
196          If a conversion is specified, convert the value before putting          The value is converted before putting it into the returned dict.
197          it into the returned dict. The following conversions are          The following conversions are available:
         available:  
198    
199             'filename' -- The attribute is a filename.             'filename' -- The attribute is a filename.
200    
# Line 173  class SessionLoader(XMLReader): Line 210  class SessionLoader(XMLReader):
210                        defined earlier in the .thuban file. Look it up                        defined earlier in the .thuban file. Look it up
211                        self.idmap. If it's the ID of a shapestore the                        self.idmap. If it's the ID of a shapestore the
212                        value will be the table of the shapestore.                        value will be the table of the shapestore.
213    
214               'idref' -- The attribute is the id of an object defined
215                          earlier in the .thuban file. Look it up self.idmap
216    
217               'ascii' -- The attribute is converted to a bytestring with
218                          ascii encoding.
219    
220               a callable -- The attribute value is passed to the callable
221                             and the return value is used as the converted
222                             value
223    
224            If no conversion is specified for an attribute it is converted
225            with self.encode.
226          """          """
227          normalized = {}          normalized = {}
228    
229          for d in descr:          for d in descr:
230              if d.required and not attrs.has_key(d.fullname):              if d.required and not attrs.has_key(d.fullname):
231                  pass                  raise LoadError("Element %s requires an attribute %r"
232              #raise LoadError("Element %s requires an attribute %r"                                  % (element, d.name))
             #                    % (element, d.name))  
233              value = attrs.get(d.fullname, d.default)              value = attrs.get(d.fullname, d.default)
234    
235              if d.conversion == "shapesource":              if d.conversion in ("idref", "shapesource"):
236                  if value in self.idmap:                  if value in self.idmap:
237                      value = self.idmap[value]                      value = self.idmap[value]
238                  else:                  else:
# Line 201  class SessionLoader(XMLReader): Line 250  class SessionLoader(XMLReader):
250                                      % (element, d.name))                                      % (element, d.name))
251              elif d.conversion == "filename":              elif d.conversion == "filename":
252                  value = os.path.abspath(os.path.join(self.GetDirectory(),                  value = os.path.abspath(os.path.join(self.GetDirectory(),
253                                                       value))                                                       self.encode(value)))
254                elif d.conversion == "ascii":
255                    value = value.encode("ascii")
256                elif d.conversion:
257                    # Assume it's a callable
258                    value = d.conversion(value)
259                else:
260                   value = self.encode(value)
261    
262              normalized[d.name] = value              normalized[d.name] = value
263          return normalized          return normalized
264    
265        def open_shapefile(self, filename):
266            """Open shapefile, with alternative path handling.
267            
268               If a shapefile cannot be opened and an IOError is raised, check for
269               an alternative. This alternative can be specified interactively by
270               the user or taken from a list of (potential) locations, depending on
271               the callback implementation.
272                
273               The alternative is rechecked. If taken from a list the user
274               has to confirm the alternative.
275            """
276    
277            # Flag if the alternative path was specified interactively / from list.
278            from_list = 0
279            while 1:
280                try:
281                    store = self.theSession.OpenShapefile(filename)
282                    if from_list:
283                        # A valid path has been guessed from a list
284                        # Let the user confirm - or select an alternative.
285                        filename, from_list = self.shapefile_callback(
286                                                filename, "check")
287                        if filename is None:
288                            # Selection cancelled
289                            raise LoadCancelled
290                        elif store.FileName() == filename:
291                            # Proposed file has been accepted
292                            break
293                        else:
294                            # the filename has been changed, try the new file
295                            pass
296                    else:
297                        break
298                except IOError:
299                    if self.shapefile_callback is not None:
300                        filename, from_list = self.shapefile_callback(
301                                                filename,
302                                                mode = "search",
303                                                second_try = from_list)
304                        if filename is None:
305                            raise LoadCancelled
306                    else:
307                        raise
308            return store
309    
310        def start_dbconnection(self, name, qname, attrs):
311            attrs = self.check_attrs(name, attrs,
312                                     [AttrDesc("id", True),
313                                      AttrDesc("dbtype", True),
314                                      AttrDesc("host", False, ""),
315                                      AttrDesc("port", False, ""),
316                                      AttrDesc("user", False, ""),
317                                      AttrDesc("dbname", True)])
318            ID = attrs["id"]
319            dbtype = attrs["dbtype"]
320            if dbtype != "postgis":
321                raise LoadError("dbtype %r not supported" % filetype)
322    
323            del attrs["id"]
324            del attrs["dbtype"]
325    
326            # Try to open the connection and if it fails ask the user for
327            # the correct parameters repeatedly.
328            # FIXME: it would be better not to insist on getting a
329            # connection here. We should handle this more like the raster
330            # images where the layers etc still are created but are not
331            # drawn in case Thuban can't use the data for various reasons
332            while 1:
333                try:
334                    conn = postgisdb.PostGISConnection(**attrs)
335                    break
336                except postgisdb.ConnectionError, val:
337                    if self.db_connection_callback is not None:
338                        attrs = self.db_connection_callback(attrs, str(val))
339                        if attrs is None:
340                            raise LoadCancelled
341                    else:
342                        raise
343    
344            self.idmap[ID] = conn
345            self.theSession.AddDBConnection(conn)
346    
347        def start_dbshapesource(self, name, qname, attrs):
348            attrs = self.check_attrs(name, attrs,
349                                     [AttrDesc("id", True),
350                                      AttrDesc("dbconn", True,
351                                               conversion = "idref"),
352                                      AttrDesc("tablename", True,
353                                               conversion = "ascii"),
354                                      # id_column and geometry_column were
355                                      # newly introduced with thuban-1.1.dtd
356                                      # where they're required.  Since we
357                                      # support the older formats too we
358                                      # have them optional here.
359                                      AttrDesc("id_column", False, "gid",
360                                               conversion = "ascii"),
361                                      AttrDesc("geometry_column", False,
362                                               conversion = "ascii")])
363            # The default value of geometry_column to use when instantiating
364            # the db shapestore is None which we currently can't easily use
365            # in check_attrs
366            geometry_column = attrs["geometry_column"]
367            if not geometry_column:
368                geometry_column = None
369            dbopen = self.theSession.OpenDBShapeStore
370            self.idmap[attrs["id"]] = dbopen(attrs["dbconn"], attrs["tablename"],
371                                             id_column = attrs["id_column"],
372                                             geometry_column=geometry_column)
373    
374      def start_fileshapesource(self, name, qname, attrs):      def start_fileshapesource(self, name, qname, attrs):
375          attrs = self.check_attrs(name, attrs,          attrs = self.check_attrs(name, attrs,
376                                    [AttrDesc("id", True),                                    [AttrDesc("id", True),
# Line 217  class SessionLoader(XMLReader): Line 382  class SessionLoader(XMLReader):
382          filetype = attrs["filetype"]          filetype = attrs["filetype"]
383          if filetype != "shapefile":          if filetype != "shapefile":
384              raise LoadError("shapesource filetype %r not supported" % filetype)              raise LoadError("shapesource filetype %r not supported" % filetype)
385          self.idmap[ID] = self.theSession.OpenShapefile(filename)          self.idmap[ID] = self.open_shapefile(filename)
386    
387      def start_derivedshapesource(self, name, qname, attrs):      def start_derivedshapesource(self, name, qname, attrs):
388          attrs = self.check_attrs(name, attrs,          attrs = self.check_attrs(name, attrs,
# Line 284  class SessionLoader(XMLReader): Line 449  class SessionLoader(XMLReader):
449          self.aMap = None          self.aMap = None
450    
451      def start_projection(self, name, qname, attrs):      def start_projection(self, name, qname, attrs):
452          self.ProjectionName = self.encode(attrs.get((None, 'name'), None))          attrs = self.check_attrs(name, attrs,
453          self.ProjectionParams = [ ]                                   [AttrDesc("name", conversion=self.encode),
454                                      AttrDesc("epsg", default=None,
455                                               conversion=self.encode)])
456            self.projection_name = attrs["name"]
457            self.projection_epsg = attrs["epsg"]
458            self.projection_params = [ ]
459    
460      def end_projection(self, name, qname):      def end_projection(self, name, qname):
461          if self.aLayer is not None:          if self.aLayer is not None:
# Line 296  class SessionLoader(XMLReader): Line 466  class SessionLoader(XMLReader):
466              assert False, "projection tag out of context"              assert False, "projection tag out of context"
467              pass              pass
468    
469          obj.SetProjection(          obj.SetProjection(Projection(self.projection_params,
470              Projection(self.ProjectionParams, self.ProjectionName))                                       self.projection_name,
471                                         epsg = self.projection_epsg))
472    
473      def start_parameter(self, name, qname, attrs):      def start_parameter(self, name, qname, attrs):
474          s = attrs.get((None, 'value'))          s = attrs.get((None, 'value'))
475          s = str(s) # we can't handle unicode in proj          s = str(s) # we can't handle unicode in proj
476          self.ProjectionParams.append(s)          self.projection_params.append(s)
477    
478      def start_layer(self, name, qname, attrs, layer_class = Layer):      def start_layer(self, name, qname, attrs, layer_class = Layer):
479          """Start a layer          """Start a layer
# Line 322  class SessionLoader(XMLReader): Line 493  class SessionLoader(XMLReader):
493          if attrs.has_key((None, "shapestore")):          if attrs.has_key((None, "shapestore")):
494              store = self.idmap[attrs[(None, "shapestore")]]              store = self.idmap[attrs[(None, "shapestore")]]
495          else:          else:
496              store = self.theSession.OpenShapefile(filename)              store = self.open_shapefile(filename)
497    
498          self.aLayer = layer_class(title, store,          self.aLayer = layer_class(title, store,
499                                    fill = fill, stroke = stroke,                                    fill = fill, stroke = stroke,
500                                    lineWidth = stroke_width,                                    lineWidth = stroke_width,
# Line 338  class SessionLoader(XMLReader): Line 510  class SessionLoader(XMLReader):
510          filename = os.path.join(self.GetDirectory(), filename)          filename = os.path.join(self.GetDirectory(), filename)
511          filename = self.encode(filename)          filename = self.encode(filename)
512          visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"          visible  = self.encode(attrs.get((None, 'visible'), "true")) != "false"
513            opacity  = float(attrs.get((None, 'opacity'), "1"))
514            masktype = str(attrs.get((None, 'masktype'), "bit"))
515    
516          self.aLayer = layer_class(title, filename, visible = visible)          masktypes = {"none": layer_class.MASK_NONE,
517                         "bit":  layer_class.MASK_BIT,
518                         "alpha": layer_class.MASK_ALPHA}
519    
520            self.aLayer = layer_class(title, filename,
521                                      visible = visible,
522                                      opacity = opacity,
523                                      masktype = masktypes[masktype])
524    
525      def end_rasterlayer(self, name, qname):      def end_rasterlayer(self, name, qname):
526          self.aMap.AddLayer(self.aLayer)          self.aMap.AddLayer(self.aLayer)
527          self.aLayer = None          self.aLayer = None
528    
529      def start_classification(self, name, qname, attrs):      def start_classification(self, name, qname, attrs):
530          field = attrs.get((None, 'field'), None)          # field and field_type are optional because the classification
531            # can also be empty, ie. have only a default.
532            attrs = self.check_attrs(name, attrs,
533                                     [AttrDesc("field", False),
534                                      AttrDesc("field_type", False)])
535    
536            field = attrs["field"]
537            fieldType = attrs["field_type"]
538    
539            if field == "": return # no need to set classification column.
540    
         fieldType = attrs.get((None, 'field_type'), None)  
541          dbFieldType = self.aLayer.GetFieldType(field)          dbFieldType = self.aLayer.GetFieldType(field)
542    
543          if fieldType != dbFieldType:          if fieldType != dbFieldType:
# Line 397  class SessionLoader(XMLReader): Line 586  class SessionLoader(XMLReader):
586          del self.cl_group, self.cl_prop          del self.cl_group, self.cl_prop
587    
588      def start_clrange(self, name, qname, attrs):      def start_clrange(self, name, qname, attrs):
589            attrs = self.check_attrs(name, attrs,
590                                     [AttrDesc("range", False, None),
591                                      AttrDesc("min", False, None),
592                                      AttrDesc("max", False, None)])
593    
594          range = attrs.get((None, 'range'), None)          range = attrs['range']
595          # for backward compatibility (min/max are not saved)          # for backward compatibility (min/max are not saved)
596          min   = attrs.get((None, 'min'), None)          min   = attrs['min']
597          max   = attrs.get((None, 'max'), None)          max   = attrs['max']
598    
599          try:          try:
600              if range is not None:              if range is not None:
# Line 424  class SessionLoader(XMLReader): Line 617  class SessionLoader(XMLReader):
617          self.aLayer.GetClassification().AppendGroup(self.cl_group)          self.aLayer.GetClassification().AppendGroup(self.cl_group)
618          del self.cl_group, self.cl_prop          del self.cl_group, self.cl_prop
619    
620    
621        def start_clpattern(self, name, qname, attrs):
622            pattern = attrs.get((None, 'pattern'), "")
623    
624            self.cl_group = ClassGroupPattern(self.encode(pattern))
625            self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
626            self.cl_prop = ClassGroupProperties()
627    
628        def end_clpattern(self, name, qname):
629            self.cl_group.SetProperties(self.cl_prop)
630            self.aLayer.GetClassification().AppendGroup(self.cl_group)
631            del self.cl_group, self.cl_prop
632    
633    
634      def start_cldata(self, name, qname, attrs):      def start_cldata(self, name, qname, attrs):
635          self.cl_prop.SetLineColor(          self.cl_prop.SetLineColor(
636              parse_color(attrs.get((None, 'stroke'), "None")))              parse_color(attrs.get((None, 'stroke'), "None")))
637          self.cl_prop.SetLineWidth(          self.cl_prop.SetLineWidth(
638              int(attrs.get((None, 'stroke_width'), "0")))              int(attrs.get((None, 'stroke_width'), "0")))
639            self.cl_prop.SetSize(int(attrs.get((None, 'size'), "5")))
640          self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))          self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
641    
642      def end_cldata(self, name, qname):      def end_cldata(self, name, qname):
# Line 438  class SessionLoader(XMLReader): Line 646  class SessionLoader(XMLReader):
646          self.aLayer = self.aMap.LabelLayer()          self.aLayer = self.aMap.LabelLayer()
647    
648      def start_label(self, name, qname, attrs):      def start_label(self, name, qname, attrs):
649          x = float(attrs[(None, 'x')])          attrs = self.check_attrs(name, attrs,
650          y = float(attrs[(None, 'y')])                                   [AttrDesc("x", True, conversion = float),
651          text = self.encode(attrs[(None, 'text')])                                    AttrDesc("y", True, conversion = float),
652          halign = attrs[(None, 'halign')]                                    AttrDesc("text", True),
653          valign = attrs[(None, 'valign')]                                    AttrDesc("halign", True,
654                                               conversion = "ascii"),
655                                      AttrDesc("valign", True,
656                                               conversion = "ascii")])
657            x = attrs['x']
658            y = attrs['y']
659            text = attrs['text']
660            halign = attrs['halign']
661            valign = attrs['valign']
662            if halign not in ("left", "center", "right"):
663                raise LoadError("Unsupported halign value %r" % halign)
664            if valign not in ("top", "center", "bottom"):
665                raise LoadError("Unsupported valign value %r" % valign)
666          self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)          self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
667    
668      def characters(self, chars):      def characters(self, chars):
669          pass          pass
670    
671    
672  def load_session(filename):  def load_session(filename, db_connection_callback = None,
673      """Load a Thuban session from the file object file"""                             shapefile_callback = None):
674        """Load a Thuban session from the file object file
675      handler = SessionLoader()  
676        The db_connection_callback, if given should be a callable object
677        that can be called like this:
678           db_connection_callback(params, message)
679    
680        where params is a dictionary containing the known connection
681        parameters and message is a string with a message why the connection
682        failed. db_connection_callback should return a new dictionary with
683        corrected and perhaps additional parameters like a password or None
684        to indicate that the user cancelled.
685        """
686        handler = SessionLoader(db_connection_callback, shapefile_callback)
687      handler.read(filename)      handler.read(filename)
688    
689      session = handler.theSession      session = handler.theSession
690      # Newly loaded session aren't modified      # Newly loaded session aren't modified
691      session.UnsetModified()      session.UnsetModified()
692    
693        handler.Destroy()
694    
695      return session      return session
696    

Legend:
Removed from v.1452  
changed lines
  Added in v.2734

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26