/[thuban]/branches/WIP-pyshapelib-bramz/Extensions/ogr/ogrshapes.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Extensions/ogr/ogrshapes.py

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

revision 2437 by silke, Wed Dec 8 17:37:30 2004 UTC revision 2559 by nhueffme, Tue Feb 8 09:52:56 2005 UTC
# Line 1  Line 1 
1  # Copyright (C) 2004 by Intevation GmbH  # Copyright (C) 2004 by Intevation GmbH
2  # Authors:  # Authors:
3  # Nina H�ffmeyer <[email protected]>  # Nina Hueffmeyer <[email protected]>
4  #  #
5  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
6  # Read the file COPYING coming with the software for details.  # Read the file COPYING coming with the software for details.
# Line 26  from Thuban.Model import table Line 26  from Thuban.Model import table
26  from Thuban.Model.data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT  from Thuban.Model.data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
27  from Thuban.Model.data import RAW_PYTHON, RAW_SHAPEFILE, RAW_WKT  from Thuban.Model.data import RAW_PYTHON, RAW_SHAPEFILE, RAW_WKT
28    
29  # Raw data in ogr format  SHAPETYPE_UNKNOWN = ogr.wkbUnknown
 RAW_OGRSHAPES = "RAW_OGRSHAPES"  
   
   
30    
31  def has_ogr_support():  def has_ogr_support():
32      """Return whether this Thuban instance supports ogr file formats      """Return whether this Thuban instance supports ogr file formats
# Line 42  def has_ogr_support(): Line 39  def has_ogr_support():
39  if ogr is not None:  if ogr is not None:
40      # mapping from ogr-lib shapetype and table constants to our constants      # mapping from ogr-lib shapetype and table constants to our constants
41      ogrlib_shapetypes = {ogr.wkbPolygon: SHAPETYPE_POLYGON,      ogrlib_shapetypes = {ogr.wkbPolygon: SHAPETYPE_POLYGON,
42                         ogr.wkbLineString: SHAPETYPE_ARC,                  ogr.wkbLineString: SHAPETYPE_ARC,
43                         ogr.wkbPoint: SHAPETYPE_POINT}                  ogr.wkbPoint: SHAPETYPE_POINT,
44                    ogr.wkbUnknown: SHAPETYPE_UNKNOWN}
45    
46      fieldtype_map = {ogr.OFTString: table.FIELDTYPE_STRING,      fieldtype_map = {ogr.OFTString: table.FIELDTYPE_STRING,
47          ogr.OFTInteger: table.FIELDTYPE_INT,                  ogr.OFTInteger: table.FIELDTYPE_INT,
48          ogr.OFTReal: table.FIELDTYPE_DOUBLE}                  ogr.OFTReal: table.FIELDTYPE_DOUBLE}
49    
50    
51  class OGRShape:  class OGRShape:
# Line 56  class OGRShape: Line 54  class OGRShape:
54    
55      def __init__(self, ogrlayer, shapeid):      def __init__(self, ogrlayer, shapeid):
56          self.ogrlayer = ogrlayer          self.ogrlayer = ogrlayer
57            self.feature = self.ogrlayer.GetFeature(shapeid)
58          self.shapeid = shapeid          self.shapeid = shapeid
59            self.geom = self.feature.GetGeometryRef()
60            self.shapetype = self.geom.GetGeometryType()
61    
62      def compute_bbox(self):      def compute_bbox(self):
63          """          """
64          Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy)          Return the bounding box of the shape as a tuple (minx,miny,maxx,maxy)
65          """          """
66          shape = self.ogrlayer.GetFeature(self.shapeid)          minx, maxx, miny, maxy = self.geom.GetEnvelope()
67          geom = shape.GetGeometryRef()          return (minx, miny, maxx, maxy)
         minx, maxx, miny, maxy = geom.GetEnvelope()  
         return (minx, miny, maxx, maxy)  
68    
69      def ShapeID(self):      def ShapeID(self):
70          return self.shapeid          return self.shapeid
71    
     # diese Methode funktioniert vielleicht noch nicht f�r andere Formate als shp  
     #(wenn ein polygon aus mehreren  
     # Ringen besteht o.�., hat ein Geometry-Objekt mehr als einen GeometryRef().----------------------------  
72      def Points(self):      def Points(self):
73          """Return the coordinates of the shape as a list of lists of pairs"""          """Return the coordinates of the shape as a list of lists of pairs"""
74          feature = self.ogrlayer.GetFeature(self.shapeid)          shape = []
75          geometry = feature.GetGeometryRef()          #spatialFilter = self.ogrlayer.GetSpatialFilter()
76          while geometry.GetGeometryCount() != 0:  
77                  geometry = geometry.GetGeometryRef(0)          #if spatialFilter is not None:
78          points = []          #self.ogrlayer.SetSpatialFilter(None)
79          for point in range(geometry.GetPointCount()):              #feature = self.ogrlayer.GetFeature(self.shapeid)
80                  x = geometry.GetX(point)              #self.ogrlayer.SetSpatialFilter(spatialFilter)
81                  y = geometry.GetY(point)          #else:
82                  points.append((x, y))          #feature = self.ogrlayer.GetFeature(self.shapeid)
83          points = [points]  
84          return points          #if feature is None:
85             #   return shape.append([])
86            #geom = feature.GetGeometryRef()
87    
88            if self.geom is None:
89                return shape.append([])
90    
91            # if geometry object is of type point or line
92            if self.geom.GetGeometryCount() == 0:
93                points =[]
94                for point in range(self.geom.GetPointCount()):
95                    x = self.geom.GetX(point)
96                    y = self.geom.GetY(point)
97                    points.append((x, y))
98                return [points]
99            # if geometry object is of type polygon or multipolygon
100            for i in range(self.geom.GetGeometryCount()):
101                points = []
102                geometry = self.geom.GetGeometryRef(i)
103                # if geometry object is polygon
104                if geometry.GetGeometryCount() == 0:
105                    for point in range(geometry.GetPointCount()):
106                        x = geometry.GetX(point)
107                        y = geometry.GetY(point)
108                        points.append((x, y))
109                    shape.append(points)
110                # if geometry object is of type multipolygon
111                else:
112                    for j in range(geometry.GetGeometryCount()):
113                        points = []
114                        subgeom = geometry.GetGeometryRef(j)
115                        for point in range(subgeom.GetPointCount()):
116                            x = subgeom.GetX(point)
117                            y = subgeom.GetY(point)
118                            points.append((x, y))
119                        shape.append(points)
120            return shape
121    
122      def RawData(self):      def RawData(self):
123          """Return the shape id to use with the shapestore"""          """Return the shape id to use with the shapestore"""
# Line 95  class OGRShape: Line 127  class OGRShape:
127          """Return the ogrlayer object"""          """Return the ogrlayer object"""
128          return self.ogrlayer          return self.ogrlayer
129    
130        def ShapeType(self):
131            return self.shapetype
132    
133    
134  class OGRShapeStore:  class OGRShapeStore:
135    
136      """Corresponds to an OGRLayer object, containing features/shapes and providing all methods Thuban      """Corresponds to an OGRLayer object, containing features/shapes and
137         needs."""         providing all methods Thuban needs.
138        """
139    
140      def __init__(self, session, filename, layername):      def __init__(self, filename, layername, id_column = None):
141          # Make the filename absolute. The filename will be          # Make the filename absolute. The filename will be
142          # interpreted relative to that anyway, but when saving a          # interpreted relative to that anyway, but when saving a
143          # session we need to compare absolute paths and it's usually          # session we need to compare absolute paths and it's usually
144          # safer to always work with absolute paths.          # safer to always work with absolute paths.
145    
146          self.filename = os.path.abspath(filename)          self.filename = filename
147          self.layername = layername          self.layername = layername
148    
149          self.ogrdatasource = ogr.Open(self.filename)          self.ogrdatasource = ogr.Open(filename)
150          self.ogrlayer = (self.ogrdatasource).GetLayerByName(layername)          self.ogrlayer = (self.ogrdatasource).GetLayerByName(layername)
151          self.table = OGRTable(self.ogrdatasource, self.ogrlayer)  
152            driver = self.ogrdatasource.GetDriver().GetName()
153            if driver == 'PostgreSQL':
154                self.id_column = 'gid'
155            else:
156                self.id_column = 'fid'
157    
158            self.table = OGRTable(self.ogrdatasource, self.ogrlayer, self.id_column)
159    
160          self._open_ogrlayer(layername)          self._open_ogrlayer(layername)
161    
162      def _open_ogrlayer(self, layername):      def _open_ogrlayer(self, layername):
163          self.numshapes = self.ogrlayer.GetFeatureCount()          self.numshapes = self.ogrlayer.GetFeatureCount()
164          self.shapetype = self.ogrlayer.GetLayerDefn().GetGeomType()          self.shapetype = self.ogrlayer.GetLayerDefn().GetGeomType()
         extent = self.ogrlayer.GetExtent()  
   
         if extent:  
                 self.bbox = [extent[0], extent[2], extent[1], extent[3]]  
         else:  
                 self.bbox = None  
165    
166          self.shapetype = ogrlib_shapetypes[self.shapetype]          extent = self.ogrlayer.GetExtent()
167            if extent:
168                self.bbox = [extent[0], extent[2], extent[1], extent[3]]
169            else:
170                self.bbox = None
171    
172            if self.shapetype is not ogr.wkbUnknown:
173                self.shapetype = ogrlib_shapetypes[self.shapetype]
174            #else:
175                # this should be ogr.wkbUnknown, but Thuban does not know how
176                # to handle an unknown shapetype (e.g. Session Tree)
177                #self.shapetype = ogrlib_shapetypes[ogr.wkbPoint]
178    
179      def OGRLayer(self):      def OGRLayer(self):
180          """Return the OGRLayer object"""          """Return the OGRLayer object"""
# Line 148  class OGRShapeStore: Line 196  class OGRShapeStore:
196          return self.shapetype          return self.shapetype
197    
198      def RawShapeFormat(self):      def RawShapeFormat(self):
199          """Return the raw data format of the shape data, i.e. RAW_OGRSHAPES"""          """Return the raw data format of the shape data, i.e. RAW_PYTHON"""
200          return RAW_OGRSHAPES          return RAW_PYTHON
201    
202      def NumShapes(self):      def NumShapes(self):
203          """Return the number of shapes in the shape store"""          """Return the number of shapes in the shape store"""
# Line 167  class OGRShapeStore: Line 215  class OGRShapeStore:
215          form (minx, miny, maxx, maxy) in the coordinate system of the          form (minx, miny, maxx, maxy) in the coordinate system of the
216          shape store.          shape store.
217    
218          The method GetFID() returns feature IDs starting from 0. In Thuban feature IDs start with 1.          The method GetFID() returns feature IDs starting from 0.
219          """          """
220          # Bind a few globals to locals to make it a bit faster          # Bind a few globals to locals to make it a bit faster
221          cls = OGRShape          cls = OGRShape
222          ogrlayer = self.ogrlayer          ogrlayer = self.ogrlayer
223    
224          left, bottom, right, top = bbox          left, bottom, right, top = bbox
225            # create a geometry which can be passed to the layer as spatial filter
226            bboxpolygon = ogr.CreateGeometryFromWkt(
227                          ('Polygon((%s %s, %s %s, %s %s,%s %s, %s %s))'
228                                     %(left, bottom, left, top, right, top,
229                                      right, bottom, left, bottom)))
230    
231            if ogrlayer.GetSpatialRef():
232                bboxpolygon.AssignSpatialReference(ogrlayer.GetSpatialRef())
233    
234            ogrlayer.ResetReading()
235            #ogrlayer.SetSpatialFilterRect(left, bottom, right, top)
236            ogrlayer.SetSpatialFilter(bboxpolygon)
237    
238            numFeatures = ogrlayer.GetFeatureCount()
239            for feature in range(numFeatures):
240                nextFeature = ogrlayer.GetNextFeature()
241                yield cls(ogrlayer, nextFeature.GetFID())
242    
243          # create a geometry which can be passed to the layer as spatial filter          ogrlayer.SetSpatialFilter(None)
244          bboxpolygon = ogr.CreateGeometryFromWkt('Polygon((%s %s, %s %s, %s %s, %s %s, %s %s))'          bboxpolygon.Destroy()
                 %(left, bottom, left, top, right, top, right, bottom, left, bottom))  
   
         if ogrlayer.GetSpatialRef():  
                 bboxpolygon.AssignSpatialReference(ogrlayer.GetSpatialRef())  
   
         ogrlayer.ResetReading()  
         ogrlayer.SetSpatialFilter(bboxpolygon)  
         numFeatures = ogrlayer.GetFeatureCount()  
         for feature in range(numFeatures):  
                 nextFeature = ogrlayer.GetNextFeature()  
                 yield cls(ogrlayer, nextFeature.GetFID())  
         ogrlayer.SetSpatialFilter(None)  
         bboxpolygon.Destroy()  
245    
246      def AllShapes(self):      def AllShapes(self):
247          """Return an iterable over the shapes in the shape store."""          """Return an iterable over the shapes in the shape store."""
248          for i in xrange(self.NumShapes()):          self.ogrlayer.ResetReading()
249              yield OGRShape(self.ogrlayer, i)          nextFeature = self.ogrlayer.GetNextFeature()
250            while nextFeature is not None:
251                yield OGRShape(self.ogrlayer, nextFeature.GetFID())
252                nextFeature = self.ogrlayer.GetNextFeature()
253    
254      def Shape(self, index):      def Shape(self, index):
255          """Return the shape with index index"""          """Return the shape with index index"""
# Line 212  class OGRShapeStore: Line 267  class OGRShapeStore:
267          """Return None."""          """Return None."""
268          return None          return None
269    
270        def Id_column(self):
271            """Return the id_column."""
272            return self.id_column
273    
274  class OGRTable:  class OGRTable:
275    
276      """A Table for an ogr file      """A Table for an ogr file
277      """      """
278    
279      def __init__(self, ds, layer):      def __init__(self, ds, layer, id_column):
280          """Initialize the OGRTable.          """Initialize the OGRTable.
281    
282             ds should be an instance of OGRDatasource.          ds should be an instance of OGRDatasource.
283             layer should be an instance of OGRLayer.          layer should be an instance of OGRLayer.
284          """          """
285    
286          self.datasource = ds          self.datasource = ds
287          self.layer = layer          self.layer = layer
288          self.tablename = layer.GetName()          self.tablename = layer.GetName()
289            self.id_column = id_column
290    
291          # Map column names and indices to column objects.          # Map column names and indices to column objects.
292          self.column_map = {}          self.column_map = {}
293    
294            # Map feature ids to ordinals.
295            self._map_ords_and_ids()
296    
297          self._fetch_table_information()          self._fetch_table_information()
298    
299      def _fetch_table_information(self):      def _fetch_table_information(self):
300          """Internal: Update information about the table"""          """Internal: Update information about the table"""
301          self.columns = []          self.columns = []
302    
303          layerdefn = self.layer.GetLayerDefn()          layerdefn = self.layer.GetLayerDefn()
304          for i in range(layerdefn.GetFieldCount()):          for i in range(layerdefn.GetFieldCount()):
305                  fielddef = layerdefn.GetFieldDefn(i)              fielddef = layerdefn.GetFieldDefn(i)
306                  fieldname = fielddef.GetName()              fieldname = fielddef.GetName()
307                  fieldtype = fieldtype_map[fielddef.GetType()]              fieldtype = fieldtype_map[fielddef.GetType()]
308                  fieldindex = layerdefn.GetFieldIndex(fieldname)              fieldindex = layerdefn.GetFieldIndex(fieldname)
309                  col = OGRColumn(fieldname, fieldtype, fieldindex)              col = OGRColumn(fieldname, fieldtype, fieldindex)
310                  if col is not None:              if col is not None:
311                          self.columns.append(col)                  self.columns.append(col)
312    
313          for col in self.columns:          for col in self.columns:
314              self.column_map[col.name] = col              self.column_map[col.name] = col
315              self.column_map[col.index] = col              self.column_map[col.index] = col
316    
317        def _map_ords_and_ids(self):
318            self.ordinals = {}
319            self.ids = {}
320    
321            lay = self.datasource.ExecuteSQL(
322                    "SELECT %s from %s"
323                    %(self.id_column, self.tablename))
324            lay.ResetReading()
325            nextFeature = lay.GetNextFeature()
326            ord = 0
327            while nextFeature is not None:
328                id = nextFeature.GetFID()
329                self.ordinals[ord] = id
330                self.ids[id] = ord
331                nextFeature = lay.GetNextFeature()
332                ord = ord + 1
333            self.datasource.ReleaseResultSet(lay)
334    
335      def TableName(self):      def TableName(self):
336          """Return the name of the table, which is the name of the layer"""          """Return the name of the table, which is the name of the layer"""
337          return self.tablename          return self.tablename
# Line 259  class OGRTable: Line 339  class OGRTable:
339      def Title(self):      def Title(self):
340          """Return the title of the table.          """Return the title of the table.
341    
342          The title is currently fixed and equal to the tablename          The title is currently
343          """          """
344          return self.tablename          return self.tablename
345    
# Line 268  class OGRTable: Line 348  class OGRTable:
348          return ()          return ()
349    
350      def NumColumns(self):      def NumColumns(self):
351          """Return the number of columns."""          """Return the number of columns."""
352          return len(self.columns)          return len(self.columns)
353    
354      def Columns(self):      def Columns(self):
355          """Return all columns."""          """Return all columns."""
356          return self.columns          return self.columns
357    
358      def Column(self, col):      def Column(self, col):
359          """Return the column col. col can be either a string or an integer."""          """Return the column col. col can be either a string or an integer."""
360          return self.column_map[col]          return self.column_map[col]
361    
362      def HasColumn(self, col):      def HasColumn(self, col):
363          """Return if column col exists. col can be either a string or an integer."""          """Return if column col exists. col can be either a string or an
364            integer.
365            """
366          return self.column_map.has_key(col)          return self.column_map.has_key(col)
367    
368      def NumRows(self):      def NumRows(self):
369          """Return the number of rows in the table, which equals the number of features in the layer."""          """Return the number of rows in the table, which equals the number of
370          return self.layer.GetFeatureCount()          features in the layer.
371            """
372            return self.layer.GetFeatureCount()
373    
374      def RowIdToOrdinal(self, gid):      def RowIdToOrdinal(self, gid):
375          """Return the row ordinal given its id"""          """Return the row ordinal given its id"""
376          return self.layer.GetFeature(gid).GetFID()          if gid < 0:
377                return gid
378            else:
379                ord = self.ids[gid]
380                return ord
381    #        lay = self.datasource.ExecuteSQL(
382     #               "SELECT COUNT(%s) From %s where FID < %s"
383      #              %(self.id_column, self.tablename, gid))
384       #     ord = lay.GetFeature(0).GetField(0)
385        #    self.datasource.ReleaseResultSet(lay)
386     #       return ord
387    
388      def RowOrdinalToId(self, num):      def RowOrdinalToId(self, num):
389          """Return the rowid for given its ordinal"""          """Return the rowid for given its ordinal"""
390          return self.layer.GetFeature(num).GetFID()  #        lay = self.datasource.ExecuteSQL(
391     #               "SELECT FID From %s"
392      #              %(self.tablename))
393       #     for i in range(num):
394        #        lay.GetNextFeature()
395         #   id = lay.GetNextFeature().GetField(0)
396          #  self.datasource.ReleaseResultSet(lay)
397            if num >= 0:
398                id = self.ordinals[num]
399                return id
400            else:
401                return num
402    
403      def ReadRowAsDict(self, row, row_is_ordinal = 0):      def ReadRowAsDict(self, row, row_is_ordinal = 0):
404          """Return a dictionary which contains all the fields."""          """Return a dictionary which contains all the fields."""
405          layerdef = self.layer.GetLayerDefn()          if row_is_ordinal == 1:
406          feature = self.layer.GetFeature(row)              rowId = self.RowOrdinalToId(row)
407          result = {}          else:
408          for i in range(feature.GetFieldCount()):              rowId = row
409                  fielddef = layerdef.GetFieldDefn(i)          layerdef = self.layer.GetLayerDefn()
410                  result[fielddef.GetName()] = feature.GetField(i)          feature = self.layer.GetFeature(rowId)
411            result = {}
412            for i in range(len(self.columns)):
413                fielddef = layerdef.GetFieldDefn(i)
414                if feature is not None:
415                    result[self.columns[i].name] = feature.GetField(i)
416                else:
417                    result[fielddef.GetName()] = None
418          return result          return result
419    
420      def ReadValue(self, row, col, row_is_ordinal = 0):      def ReadValue(self, row, col, row_is_ordinal = 0):
421          """Return the requested value."""          """Return the requested value."""
422          if col is None:          if col is None:
423                  return None              return None
424          else:          else:
425                  feature = self.layer.GetFeature(row)              feature = self.layer.GetFeature(row)
426                  return feature.GetField(col)          return feature.GetField(col)
427    
428      def ValueRange(self, col):      def ValueRange(self, col):
429          """Return the value range of the given column (given as string)."""          """Return the value range of the given column (given as string)."""
430          result = self.datasource.ExecuteSQL("SELECT min(%s), max(%s) FROM %s"          result = self.datasource.ExecuteSQL("SELECT min(%s), max(%s) FROM %s"
431                  %(col, col, self.layer.GetName()))                  %(col, col, self.layer.GetName()))
432          result.ResetReading()          result.ResetReading()
433          feature = result.GetNextFeature()          feature = result.GetNextFeature()
434          try:          try:
435                  min = feature.GetField(0)              min = feature.GetField(0)
436                  max = feature.GetField(1)              max = feature.GetField(1)
437          except:          except:
438                  min = 0              min = 0
439                  max = 0              max = 0
440          self.datasource.ReleaseResultSet(result)          self.datasource.ReleaseResultSet(result)
441          return (min, max)          return (min, max)
442    
443      def UniqueValues(self, col):      def UniqueValues(self, col):
444          """Return all the values being found in the column (given as string)."""          """Return all the values being found in the column (given as string).
445          result = self.datasource.ExecuteSQL("SELECT DISTINCT %s FROM %s ORDER BY %s" %(col, self.layer.GetName(),col))          """
446          values = []          result = self.datasource.ExecuteSQL((
447          while 1:                      "SELECT DISTINCT %s FROM %s ORDER BY %s"
448                  feature = result.GetNextFeature()                       %(col,self.layer.GetName(),col)))
449                  if feature is None:          values = []
450                          break          while 1:
451                  values.append(feature.GetField(0))              feature = result.GetNextFeature()
452          self.datasource.ReleaseResultSet(result)              if feature is None:
453                    break
454                values.append(feature.GetField(0))
455            self.datasource.ReleaseResultSet(result)
456          return values          return values
457    
458      def SimpleQuery(self, left, comparison, right):      def SimpleQuery(self, left, comparison, right):
459          """Return the FIDs resulting from the given query."""          """Return the FIDs resulting from the given query."""
460          if comparison not in ("==", "!=", "<", "<=", ">=", ">"):          if comparison not in ("==", "!=", "<", "<=", ">=", ">"):
461              raise ValueError("Comparison operator %r not allowed" % comparison)              raise ValueError("Comparison operator %r not allowed" %comparison)
462    
463          if comparison == "==":          if comparison == "==":
464              comparison = "="              comparison = "="
# Line 353  class OGRTable: Line 468  class OGRTable:
468          else:          else:
469              right_template = right              right_template = right
470    
471          query = "SELECT FID FROM %s WHERE '%s' %s %s ORDER BY FID" %(self.tablename, left.name, comparison, right_template)          query = ("SELECT %s FROM %s WHERE '%s' %s %s ORDER BY FID"
472                    % (self.id_column, self.tablename,left.name, comparison,
473                       right_template))
474    
475          lay = self.datasource.ExecuteSQL(query)          lay = self.datasource.ExecuteSQL(query)
476          result = []          result = []
477          while 1:          while lay is not None:
478              feature = lay.GetNextFeature()              feature = lay.GetNextFeature()
479              if feature is None:              if feature is None:
480                  break                  break
481              result.append(feature.GetField(0))              result.append(feature.GetField(0))
482          self.datasource.ReleaseResultSet(lay)          if lay is not None:
483                self.datasource.ReleaseResultSet(lay)
484          return result          return result
485    
486        def Id_column(self):
487            """Return the id_column."""
488            return self.id_column
489    
490    
491  class OGRColumn:  class OGRColumn:
492    

Legend:
Removed from v.2437  
changed lines
  Added in v.2559

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26