root/MGET/Branches/Jason/PythonPackage/src/GeoEco/Datasets/ArcGIS.py @ 903

Revision 903, 153.5 KB (checked in by jjr8, 16 months ago)

* Incremented build number
* Continuing work on SpatiaLite? support
* Added OSU mesoscale eddies class

Line 
1# Datasets/ArcGIS.py - Classes that define the interfaces used to
2# access tabular and raster datasets using ArcGIS APIs.
3#
4# Copyright (C) 2010 Jason J. Roberts
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License
8# as published by the Free Software Foundation; either version 2
9# of the License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License (available in the file LICENSE.TXT)
15# for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21import os
22import types
23
24from GeoEco.ArcGIS import GeoprocessorManager
25from GeoEco.Datasets import CollectibleObject, Dataset, DatasetCollection, QueryableAttribute, Grid, Database, Table, Field, SelectCursor, UpdateCursor, InsertCursor
26from GeoEco.Datasets.Collections import DatasetCollectionTree
27from GeoEco.Datasets.GDAL import GDALDataset, GDALRasterBand
28from GeoEco.DynamicDocString import DynamicDocString
29from GeoEco.Internationalization import _
30
31
32class ArcGISTabularLayer(Table):
33    __doc__ = DynamicDocString()
34
35    # Public classmethods for opening, creating, and deleting datasets
36
37    @classmethod
38    def Open(cls, name):
39        cls.__doc__.Obj.ValidateMethodInvocation()
40        return ArcGISTabularLayer(name)
41
42    @classmethod
43    def Create(cls, workspace, name, geometryType=None, srType=None, sr=None, configKeyword=None, spatialGrid1=None, spatialGrid2=None, spatialGrid3=None, autoDeleteFieldAddedByArcGIS=True):
44        cls.__doc__.Obj.ValidateMethodInvocation()
45
46        # If geometryType is None, create a table.
47
48        if isinstance(cls, ArcGISTabularLayer):
49            className = cls.__class__.__name__
50        else:
51            className = cls.__name__
52           
53        gp = GeoprocessorManager.GetWrappedGeoprocessor()
54        if geometryType is None:
55            cls._LogDebug(_(u'%(class)s: Creating table "%(name)s" in workspace "%(ws)s" with configKeyword=%(kw)s.'), {u'class': className, u'name': name, u'ws': workspace, u'kw': unicode(configKeyword)})
56            try:
57                createdName = gp.CreateTable_management(workspace, name, None, configKeyword)
58            except Exception, e:
59                raise RuntimeError(_(u'Failed to create table "%(name)s" in ArcGIS workspace "%(ws)s" due to %(e)s: %(msg)s') % {u'name': name, u'ws': workspace, u'e': e.__class__.__name__, u'msg': unicode(e)})
60
61        # Otherwise create a feature class.
62
63        else:
64            if geometryType == 'point':
65                shapeType = 'POINT'
66                hasZ = 'DISABLED'
67            elif geometryType == 'point25d':
68                shapeType = 'POINT'
69                hasZ = 'ENABLED'
70            elif geometryType == 'multipoint':
71                shapeType = 'MULTIPOINT'
72                hasZ = 'DISABLED'
73            elif geometryType == 'multipoint25d':
74                shapeType = 'MULTIPOINT'
75                hasZ = 'ENABLED'
76            elif geometryType in ['linestring', 'multilinestring']:
77                shapeType = 'POLYLINE'
78                hasZ = 'DISABLED'
79            elif geometryType in ['linestring25d', 'multilinestring25d']:
80                shapeType = 'POLYLINE'
81                hasZ = 'ENABLED'
82            elif geometryType in ['polygon', 'multipolygon']:
83                shapeType = 'POLYGON'
84                hasZ = 'DISABLED'
85            elif geometryType in ['polygon25d', 'multipolygon25d']:
86                shapeType = 'POLYGON'
87                hasZ = 'ENABLED'
88            else:
89                raise ValueError(_(u'Cannot create feature class "%(name)s" in ArcGIS workspace "%(ws)s" with the geometry type %(gt)s. ArcGIS does not support that geometry type.') % {u'name': name, u'workspace': workspace, u'gt': geometryType})
90
91            sr = cls.ConvertSpatialReference(srType, sr, 'arcgis')
92
93            cls._LogDebug(_(u'%(class)s: Creating feature class "%(name)s" in workspace "%(ws)s" with shapeType=%(st)s, hasZ=%(hasZ)s, spatialReference=%(sr)s, configKeyword=%(kw)s, spatialGrid1=%(sg1)s, spatialGrid2=%(sg1)s, spatialGrid3=%(sg1)s.'),
94                          {u'class': className, u'name': name, u'ws': workspace, u'st': shapeType, u'hasZ': hasZ, u'sr': unicode(sr), u'kw': unicode(configKeyword), u'sg1': unicode(spatialGrid1), u'sg2': unicode(spatialGrid2), u'sg3': unicode(spatialGrid3)})
95           
96            try:
97                createdName = gp.CreateFeatureClass_management(workspace, name, shapeType, None, 'DISABLED', hasZ, sr, configKeyword, spatialGrid1, spatialGrid2, spatialGrid3)
98            except Exception, e:
99                raise RuntimeError(_(u'Failed to create feature class "%(name)s" in ArcGIS workspace "%(ws)s" due to %(e)s: %(msg)s') % {u'name': name, u'ws': workspace, u'e': e.__class__.__name__, u'msg': unicode(e)})
100
101        # Instantiate an ArcGISTabularLayer for the newly-created
102        # table or feature class.
103
104        return ArcGISTabularLayer(createdName, autoDeleteFieldAddedByArcGIS)
105
106    @classmethod
107    def Delete(cls, name, dataType=None, failIfDoesNotExist=False):
108        cls.__doc__.Obj.ValidateMethodInvocation()
109
110        # Validate that the object exists and that it is a table-like
111        # object (i.e. it has fields).
112
113        if isinstance(cls, ArcGISTabularLayer):
114            className = cls.__class__.__name__
115        else:
116            className = cls.__name__
117
118        gp = GeoprocessorManager.GetWrappedGeoprocessor()
119        if not gp.Exists(name):
120            if not failIfDoesNotExist:
121                cls._LogDebug(_(u'%(class)s: Not deleting "%(name)s" because ArcGIS reports that it does not exist.'), {u'class': className, u'name': name})
122                return
123            raise ValueError(_(u'Cannot delete "%(name)s" because ArcGIS reports that it does not exist.') % {u'class': className, u'name': name})
124
125        d = gp.Describe(name)
126        if not hasattr(d, 'Fields') or d.Fields is None or d.Fields.Next() is None:
127            raise ValueError(_(u'Cannot delete "%(name)s" because it is not an ArcGIS tabular dataset. ArcGIS reports that this object does not have any fields.') % {u'name': name})
128
129        # If it is table view, ArcGIS versions 9.2 through 9.3.1
130        # cannot delete it (I am not sure about later versions). In
131        # that case, just report a warning and return. For more info,
132        # see http://forums.esri.com/Thread.asp?c=93&f=1729&t=224956.
133
134        if d.DataType.lower() == 'tableview' and GeoprocessorManager.GetArcGISMajorVersion() == 9 and GeoprocessorManager.GetArcGISMinorVersion() in [2, 3]:
135            self._LogWarning(_(u'Cannot delete ArcGIS TableView "%(name)s" because this version of ArcGIS does not allow table views to be deleted. For more information, please see http://forums.esri.com/Thread.asp?c=93&f=1729&t=224956.'), {u'name': name})
136            return
137
138        # Delete the object.
139
140        if dataType is not None:
141            cls._LogDebug(_(u'%(class)s: Deleting %(dt)s "%(name)s".'), {u'class': className, u'dt': dataType, u'name': name})
142        else:
143            cls._LogDebug(_(u'%(class)s: Deleting "%(name)s".'), {u'class': className, u'name': name})
144
145        try:
146            gp.Delete_management(name, dataType)
147        except Exception, e:
148            raise RuntimeError(_(u'Failed to delete "%(name)s" due to %(e)s: %(msg)s') % {u'name': name, u'e': e.__class__.__name__, u'msg': unicode(e)})
149
150    # Public properties and instance methods
151
152    def _GetName(self):
153        return self._Name
154
155    Name = property(_GetName, doc=DynamicDocString())
156
157    def _GetDataType(self):
158        return self._DataType
159
160    DataType = property(_GetDataType, doc=DynamicDocString())
161
162    def _GetPhysicalDataType(self):
163        return self._PhysicalDataType
164
165    PhysicalDataType = property(_GetPhysicalDataType, doc=DynamicDocString())
166
167    def _GetAutoDeleteFieldAddedByArcGIS(self):
168        return self._AutoDeleteFieldAddedByArcGIS
169
170    AutoDeleteFieldAddedByArcGIS = property(_GetAutoDeleteFieldAddedByArcGIS, doc=DynamicDocString())
171
172    # Overridden private base class instance methods
173
174    @classmethod
175    def _TestCapability(cls, capability):
176        if capability in ['setspatialreference', 'addfield', 'deletefield', 'selectcursor', 'updatecursor', 'insertcursor', 'updaterow', 'deleterow']:
177            return None
178
179        if capability in ['int16 datatype', 'int32 datatype', 'float32 datatype', 'float64 datatype', 'string datatype']:
180            return None
181
182        if capability == 'date datatype':
183            if isinstance(cls, ArcGISTabularLayer) and cls._PhysicalDataType.lower() not in ['shapefile', 'dbftable']:
184                return TypeError(_('Cannot create a field of data type "date" in %(dn)s because that data type is not supported by ArcGIS for the underlying database or file format. Try the data type "datetime" instead.') % {u'dn': cls.DisplayName})
185            return None
186
187        if capability == 'datetime datatype':
188            if isinstance(cls, ArcGISTabularLayer) and cls._PhysicalDataType.lower() in ['shapefile', 'dbftable']:
189                return TypeError(_('Cannot create a field of data type "datetime" in %(dn)s because that data type is not supported by DBF files. DBF files can only store dates, not dates with times. Try the data type "date" instead.') % {u'dn': cls.DisplayName})
190            return None
191
192        if capability == 'binary datatype':
193            if isinstance(cls, ArcGISTabularLayer) and cls._PhysicalDataType.lower() in ['shapefile', 'dbftable']:
194                return TypeError(_('Cannot create a field of data type "binary" in %(dn)s because that data type is not supported by DBF files.') % {u'dn': cls.DisplayName})
195            return None
196
197        if capability.endswith(' datatype'):
198            if isinstance(cls, ArcGISTabularLayer):
199                return TypeError(_('Cannot create a field of data type "%(dt)s" in %(dn)s because that data type is not supported.') % {u'dt': capability.split()[0], u'dn': cls.DisplayName})
200            else:
201                return TypeError(_('Cannot create a field of data type "%(dt)s" because that data type is not supported by ArcGIS.') % {u'dt': capability.split()[0]})
202
203        if capability.endswith(' isnullable'):
204            if isinstance(cls, ArcGISTabularLayer) and cls._PhysicalDataType.lower() in ['shapefile', 'dbftable'] and not capability.startswith('date '):
205                return TypeError(_('Cannot create a nullable field in %(dn)s because shapefiles and DBF files do not support null values, except in date fields.') % {u'dn': cls.DisplayName})
206            return None
207
208        if isinstance(cls, ArcGISTabularLayer):
209            return RuntimeError(_(u'The %(cls)s class does not support the "%(cap)s" capability.') % {u'cls': cls.__class__.__name__, u'cap': capability})
210        return RuntimeError(_(u'The %(cls)s class does not support the "%(cap)s" capability.') % {u'cls': cls.__name__, u'cap': capability})
211
212    @classmethod
213    def _GetSRTypeForSetting(cls):
214        return 'ArcGIS'
215
216    def _SetSpatialReference(self, sr):
217        GeoprocessorManager.GetWrappedGeoprocessor().DefineProjection_management(self._Name, sr)
218
219    def _AddField(self, name, dataType, length, precision, isNullable):
220        if dataType == 'int16':
221            dataType = 'SHORT'
222        elif dataType == 'int32':
223            dataType = 'LONG'
224        elif dataType == 'float32':
225            dataType = 'FLOAT'
226        elif dataType == 'float64':
227            dataType = 'DOUBLE'
228        elif dataType == 'string':
229            dataType = 'TEXT'
230        elif dataType == 'date' or dataType == 'datetime':
231            dataType = 'DATE'
232        elif dataType == 'binary':
233            dataType = 'BLOB'
234        else:
235            raise NotImplementedError(_(u'The _AddField method of class %(cls)s does not support the %(dt)s data type.') % {u'cls': self.__class__.__name__, u'dt': dataType})
236
237        gp = GeoprocessorManager.GetWrappedGeoprocessor()
238        newName = gp.ValidateFieldName(name, self._Name)
239        if newName != name:
240            raise ValueError(_(u'The name "%(name)s" is prohibited by either ArcGIS or the underlying database or file format. ArcGIS suggested the name "%(newName)s" instead.') % {u'name': name, u'newName': newName})
241
242        gp.AddField_management(self._Name, name, dataType)
243
244        if self.AutoDeleteFieldAddedByArcGIS and not self._DeletedFieldAddedByArcGIS:
245            if self.DataType.lower() == 'shapefile' and self.GetFieldByName('Id') is not None:
246                self.DeleteField('Id')
247            if self.DataType.lower() == 'dbasetable' and self.GetFieldByName('Field1') is not None:
248                self.DeleteField('Field1')
249            self._DeletedFieldAddedByArcGIS = True
250
251    def _DeleteField(self, name):
252        GeoprocessorManager.GetWrappedGeoprocessor().DeleteField_management(self._Name, name)
253
254    def _GetRowCount(self):
255        return GeoprocessorManager.GetWrappedGeoprocessor().GetCount_management(self._Name)
256
257    # Other private properties and instance methods
258
259    def __init__(self, name, autoDeleteFieldAddedByArcGIS=False):
260
261        # Validate that the dataset exists. Obtain a geoprocessor
262        # Describe object and validate that the dataset has fields.
263
264        gp = GeoprocessorManager.GetWrappedGeoprocessor()
265        if not gp.Exists(name):
266            raise ValueError(_(u'Failed to open ArcGIS dataset "%(name)s". ArcGIS reports that the dataset does not exist.') % {u'name': name})
267
268        d = gp.Describe(name)
269        if not hasattr(d, 'Fields') or d.Fields is None or d.Fields.Next() is None:
270            raise ValueError(_(u'Failed to open "%(name)s" as an ArcGIS tabular dataset. ArcGIS reports that this object does not have any fields.') % {u'name': name})
271
272        # Initialize properties.
273
274        self._Name = name
275        self._DataType = d.DataType
276        self._PhysicalDataType = gp.Describe(d.CatalogPath).DataType
277        self._AutoDeleteFieldAddedByArcGIS = autoDeleteFieldAddedByArcGIS and d.DataType.lower() in ['shapefile', 'dbasetable']
278        self._DeletedFieldAddedByArcGIS = False
279
280        # Initialize the base class from parameters of the Describe
281        # object.
282
283        if self._DataType == self._PhysicalDataType:
284            displayName = _(u'ArcGIS %(dt)s "%(name)s"') % {u'dt': self._DataType, u'name': name}
285        else:
286            displayName = _(u'ArcGIS %(dt)s "%(name)s" of %(pdt)s "%(cp)s"') % {u'dt': self._DataType, u'name': name, u'pdt': self._PhysicalDataType, u'cp': d.CatalogPath}
287        self._LogDebug(_(u'%(class)s 0x%(id)08X: Opening %(dn)s.'), {u'class': self.__class__.__name__, u'id': id(self), u'dn': displayName})
288
289        hasOID = hasattr(d, 'HasOID') and bool(d.HasOID)
290       
291        if hasattr(d, 'OIDFieldName') and isinstance(d.OIDFieldName, basestring):
292            oidFieldName = unicode(d.OIDFieldName)
293        else:
294            oidFieldName = None
295
296        geometryType = None
297        if hasattr(d, 'ShapeType') and isinstance(d.ShapeType, basestring):
298            shapeType = d.ShapeType.lower()
299            if shapeType == 'point':
300                if hasattr(d, 'HasZ') and bool(d.HasZ):
301                    geometryType = u'Point25D'
302                else:
303                    geometryType = u'Point'
304            elif shapeType == 'multipoint':
305                if hasattr(d, 'HasZ') and bool(d.HasZ):
306                    geometryType = u'MultiPoint25D'
307                else:
308                    geometryType = u'MultiPoint'
309            elif shapeType == 'polyline':
310                if hasattr(d, 'HasZ') and bool(d.HasZ):
311                    geometryType = u'MultiLineString25D'
312                else:
313                    geometryType = u'MultiLineString'
314            elif shapeType == 'polygon':
315                if hasattr(d, 'HasZ') and bool(d.HasZ):
316                    geometryType = u'MultiPolygon25D'
317                else:
318                    geometryType = u'MultiPolygon'
319            else:
320                self._LogWarning(_(u'The %(dn)s has an unsupported shape type "%(st)s". Geometry will not be available for this dataset.'), {u'dn': displayName, u'st': d.ShapeType})
321
322        geometryFieldName = None
323        if geometryType is not None:
324            if hasattr(d, 'ShapeFieldName') and isinstance(d.ShapeFieldName, basestring) and len(d.ShapeFieldName) > 0:
325                geometryFieldName = d.ShapeFieldName
326            else:
327                self._LogWarning(_(u'ArcGIS reported that %(dn)s has %(geom)s geometry but could not provide the name of the shape field. Geometry will not be available for this dataset.'), {u'dn': displayName, u'geom': geometryType})
328                geometryType = None
329
330        if geometryType is not None:
331            srType = 'arcgis'
332            sr = gp.CreateSpatialReference_management(d.SpatialReference).split(';')[0]
333        else:
334            srType = None
335            sr = None
336
337        if d.DataType.lower() in ['dbasetable', 'textfile', 'shapefile']:
338            maxStringLength = 254
339        else:
340            maxStringLength = None
341
342        fields = []
343        fieldsObj = d.Fields
344        f = fieldsObj.Next()
345        while f is not None:
346            dataType = f.Type.lower()
347            if dataType == 'oid':
348                dataType = 'oid'
349            elif dataType == 'geometry':
350                dataType = 'geometry'
351            elif dataType == 'smallinteger':
352                dataType = 'int16'
353            elif dataType == 'integer':
354                dataType = 'int32'
355            elif dataType == 'single':
356                dataType = 'float32'
357            elif dataType == 'double':
358                dataType = 'float64'
359            elif dataType == 'string':
360                dataType = 'string'
361            elif dataType == 'date':
362                if d.DataType.lower() in ['dbasetable', 'shapefile']:
363                    dataType = 'date'
364                else:
365                    dataType = 'datetime'
366            elif dataType == 'blob':
367                dataType = 'binary'
368            else:
369                dataType = 'unknown'
370            isNullable = f.IsNullable
371            isNullable = isinstance(isNullable, (types.BooleanType, types.IntType)) and bool(isNullable) or isinstance(isNullable, basestring) and isNullable.lower() == 'true'
372            isSettable = dataType != 'oid' and not (f.Name.lower() == 'shape_length' and geometryType in [u'MultiLineString', u'MultiLineString25D', u'MultiPolygon', u'MultiPolygon25D']) and not (f.Name.lower() == 'shape_area' and geometryType in [u'MultiPolygon', u'MultiPolygon25D'])
373            fields.append(Field(f.Name, dataType, f.Length, f.Precision, isNullable, isSettable))
374            f = fieldsObj.Next()
375
376        super(ArcGISTabularLayer, self).__init__(displayName, hasOID, oidFieldName, geometryType, geometryFieldName, srType, sr, maxStringLength, fields, ArcGISSelectCursor, ArcGISUpdateCursor, ArcGISInsertCursor)
377
378
379class ArcGISWorkspace(DatasetCollectionTree, Database):
380    __doc__ = DynamicDocString()
381
382    def _GetPath(self):
383        return self._Path
384
385    Path = property(_GetPath, doc=DynamicDocString())
386
387    def _GetDatasetType(self):
388        return self._DatasetType
389
390    DatasetType = property(_GetDatasetType, doc=DynamicDocString())
391
392    def _GetCacheTree(self):
393        return self._CacheTree
394
395    CacheTree = property(_GetCacheTree, doc=DynamicDocString())
396
397    def __init__(self, path, datasetType, pathParsingExpressions=None, pathCreationExpressions=None, cacheTree=True, queryableAttributes=None, queryableAttributeValues=None, lazyPropertyValues=None, cacheDirectory=None):
398        # TODO: self.__doc__.Obj.ValidateMethodInvocation()
399
400        # Validate datasetType.
401
402        if not issubclass(datasetType, (ArcGISRaster, ArcGISTable)):
403            raise TypeError(_(u'datasetType must be an ArcGISRaster or ArcGISTable, or a subclass of one of them.'))
404
405        # If the path exists, validate that it is an
406        # appropriately-typed ArcGIS object.
407
408        gp = GeoprocessorManager.GetWrappedGeoprocessor()
409       
410        if gp.Exists(path):
411            d = gp.Describe(path)
412            if not(d.DataType.lower() in [u'workspace', u'folder'] or
413                   issubclass(datasetType, ArcGISRaster) and d.DataType.lower() == u'rastercatalog' or
414                   issubclass(datasetType, ArcGISTable) and d.DataType.lower() == u'featuredataset'):
415                raise ValueError(_(u'Failed to open "%(path)s" as an ArcGIS workspace. ArcGIS reports that it is a %(dt)s, which cannot be opened as a workspace.') % {u'path': path, u'dt': d.DataType})
416            self._DisplayName = _(u'ArcGIS %(dt)s %(path)s') % {u'dt': d.DataType, u'path': path}
417        else:
418            self._DisplayName = _(u'ArcGIS Folder or Workspace %(path)s') % {u'path': path}
419
420        # Initialize our properties.
421
422        self._Path = path
423        self._DatasetType = datasetType
424        self._CacheTree = cacheTree
425        if self._CacheTree:
426            self._TreeContentsCache = {}
427        else:
428            self._TreeContentsCache = None
429        self._TreeDataTypeCache = {}
430
431        # Initialize the base class.
432
433        super(ArcGISWorkspace, self).__init__(pathParsingExpressions, pathCreationExpressions, queryableAttributes=queryableAttributes, queryableAttributeValues=queryableAttributeValues, lazyPropertyValues=lazyPropertyValues, cacheDirectory=cacheDirectory)
434
435    def _GetDisplayName(self):
436        return self._DisplayName
437
438    @classmethod
439    def _TestCapability(cls, capability):
440        if capability in ['createtable', 'deletetable']:
441            return None
442
443        capList = capability.split(' ', 2)
444        if len(capList) == 3 and capList[0] == 'geometrytype':
445            if capList[1] in ['point', 'point25d', 'linestring', 'linestring25d', 'polygon', 'polygon25d', 'multipoint', 'multipoint25d', 'multilinestring', 'multilinestring25d', 'multipolygon', 'multipolygon25d']:
446                return None
447            return RuntimeError(_(u'Cannot create table %(table)s with "%(geom)s" geometry in %(dn)s. ArcGIS does not support that geometry type.') % {u'tableName': capList[2], u'geom': capList[1], u'dn': cls._DisplayName})
448       
449        if isinstance(cls, ArcGISWorkspace):
450            return RuntimeError(_(u'The %(cls)s class does not support the "%(cap)s" capability.') % {u'cls': cls.__class__.__name__, u'cap': capability})
451        return RuntimeError(_(u'The %(cls)s class does not support the "%(cap)s" capability.') % {u'cls': cls.__name__, u'cap': capability})
452
453    def _ListContents(self, pathComponents):
454
455        # If we are supposed to cache the tree, probe our cache for
456        # the contents of this path.
457
458        path = os.path.join(self.Path, *pathComponents)
459
460        if self._CacheTree and path in self._TreeContentsCache:
461            self._LogDebug(_(u'%(class)s 0x%(id)08X: Retrieved cached contents of %(dt)s %(path)s'), {u'class': self.__class__.__name__, u'id': id(self), u'dt': self._TreeDataTypeCache[path], u'path': path})
462            return self._TreeContentsCache[path]
463
464        # We did not retrieve the contents of this path from the
465        # cache. Get the contents from ArcGIS.
466
467        gp = GeoprocessorManager.GetWrappedGeoprocessor()
468       
469        d = gp.Describe(path)
470        self._TreeDataTypeCache[path] = d.DataType
471       
472        self._LogDebug(_(u'%(class)s 0x%(id)08X: Listing contents of %(dt)s %(path)s'), {u'class': self.__class__.__name__, u'id': id(self), u'dt': d.DataType, u'path': path})
473
474        contents = []
475
476        # Change the geoprocessor's workspace to the path, so we can
477        # use the geoprocessor's List functions. If the workspace
478        # hasn't been set, ArcGIS 9.2 will return None for the
479        # workspace property, but will raise an exception if we set it
480        # to None. To work around this screwy design, we convert None
481        # to '', which is what ArcGIS 9.1 returns when the workspace
482        # hasn't been set.
483
484        oldWorkspace = gp.Workspace
485        if oldWorkspace is None:
486            oldWorkspace = ''
487        gp.Workspace = path
488
489        try:
490            # If we have not reached the lowest-level path component,
491            # enumerate ArcGIS objects that are containers.
492
493            if len(pathComponents) < len(self.PathParsingExpressions) - 1:
494                if d.DataType.lower() == u'folder':
495                    enum = gp.ListWorkspaces('*')
496                    workspace = enum.Next()
497                    while isinstance(workspace, basestring) and len(workspace) > 0:
498                        d = gp.Describe(workspace)
499                        if d.DataType.lower() == u'workspace' or d.DataType.lower() == u'folder' and (os.path.basename(workspace) != 'info' or not os.path.exists(os.path.join(path, workspace, 'arc.dir'))):
500                            workspace = os.path.basename(workspace)
501                            contents.append(workspace)
502                            self._TreeDataTypeCache[os.path.join(path, workspace)] = d.DataType
503                        workspace = enum.Next()
504
505                elif d.DataType.lower() == u'workspace':        # If the current path is a workspace, then enumerate raster catalogs or feature datasets in the workspace
506                    enum = gp.ListDatasets('*')
507                    dataset = enum.Next()
508                    while isinstance(dataset, basestring) and len(dataset) > 0:
509                        d = gp.Describe(dataset)
510                        if issubclass(self._DatasetType, ArcGISRaster) and d.DataType.lower() == u'rastercatalog' or issubclass(self._DatasetType, ArcGISTable) and d.DataType.lower() == u'featuredataset':
511                            dataset = os.path.basename(dataset)
512                            contents.append(dataset)
513                            self._TreeDataTypeCache[os.path.join(path, dataset)] = d.DataType
514                        dataset = enum.Next()
515
516            # Otherwise (we have reached the lowest-level path
517            # component), enumerate the type of object we're
518            # ultimately looking for.
519
520            elif issubclass(self._DatasetType, ArcGISRaster):
521
522                # Raster catalogs require special processing.
523                #
524                # TODO: Read raster catalogs using the tabular API and
525                # create queryable attributes for each field of the
526                # raster catalog.
527               
528                if d.DataType.lower() == u'rastercatalog':
529                    enum = gp.ListDatasets('*')
530                    raster = enum.Next()
531                    while isinstance(raster, basestring) and len(raster) > 0:
532                        if raster.startswith(os.path.basename(path) + '\\') or raster.startswith(os.path.basename(path) + '/'):     # Delete redundant raster catalog name from beginning of raster name, if present. Looks like an ArcGIS bug. Found in 9.3.1; other versions not tested. Causes problems later with multiband rasters.
533                            raster = raster[len(os.path.basename(path))+1:]
534                        contents.append(raster)
535                        raster = enum.Next()
536
537                # Other containers of rasters (directories,
538                # geodatabases) do not require special processing.
539               
540                else:
541                    enum = gp.ListRasters('*')
542                    raster = enum.Next()
543                    while isinstance(raster, basestring) and len(raster) > 0:
544                        contents.append(os.path.basename(raster))
545                        raster = enum.Next()
546
547            elif issubclass(self._DatasetType, ArcGISTable):
548
549                # If the caller is looking for ArcGISTables, enumerate
550                # the feature classes. Additionally, if the path does
551                # not resolve to a FeatureDataset, enumerate the
552                # tables.
553               
554                enum = gp.ListFeatureClasses('*')
555                featureClass = enum.Next()
556                while isinstance(featureClass, basestring) and len(featureClass) > 0:
557                    contents.append(os.path.basename(featureClass))
558                    featureClass = enum.Next()
559
560                if d.DataType.lower() != u'featuredataset':
561                    enum = gp.ListTables('*')
562                    table = enum.Next()
563                    while isinstance(table, basestring) and len(table) > 0:
564                        contents.append(os.path.basename(table))
565                        table = enum.Next()
566
567        # Change the geoprocessor's workspace back to what it was.
568
569        finally:
570            gp.Workspace = oldWorkspace
571
572        # Sort the contents and add them to the cache, if required.
573       
574        contents.sort()
575       
576        if self._CacheTree:
577            self._TreeContentsCache[path] = contents
578
579        return contents
580
581    def _ConstructFoundObject(self, pathComponents, attrValues, options):
582
583        # If we're looking for rasters and the path components point
584        # to a file system object that is not inside a file
585        # geodatabase, construct and return a GDALDataset. Accessing
586        # rasters with GDAL is much faster than the ArcGIS
587        # geoprocessor, and is the only way we can read and write data
588        # (at least prior to ArcGIS 10, which theoretically allows it
589        # via the geoprocessor).
590        #
591        # Note that we retrieve the raster's SpatialReference using
592        # the ArcGIS geoprocessor to work around the unfortunate fact
593        # that GDAL does not know how to recognize some of the
594        # ESRI-specific WKT strings that ArcGIS stores in rasters.
595
596        if issubclass(self._DatasetType, ArcGISRaster) and os.path.exists(os.path.join(self.Path, *pathComponents)) and self._TreeDataTypeCache[os.path.join(self.Path, *pathComponents[:-1])].lower() == u'folder':
597            gp = GeoprocessorManager.GetWrappedGeoprocessor()
598            try:
599                sr = gp.CreateSpatialReference_management(gp.Describe(os.path.join(self.Path, *pathComponents)).SpatialReference).split(';')[0]
600            except:
601                sr = gp.CreateSpatialReference_management(gp.Describe(os.path.join(self.Path, *pathComponents)).SpatialReference).split(';')[0]     # Sometimes Arc 10 fails randomly with RuntimeError: DescribeData: Method SpatialReference does not exist. Try again.
602            spatialReference = Dataset.ConvertSpatialReference('arcgis', sr, 'obj')
603            return GDALDataset(os.path.join(*pathComponents), parentCollection=self, queryableAttributeValues=attrValues, lazyPropertyValues={'SpatialReference': spatialReference}, cacheDirectory=self.CacheDirectory, **options)
604           
605        # Otherwise construct and return an object of the type
606        # specified to our own constructor.
607       
608        return self.DatasetType(os.path.join(*pathComponents), parentCollection=self, queryableAttributeValues=attrValues, cacheDirectory=self.CacheDirectory, **options)
609
610    def _GetLocalFile(self, pathComponents):
611        return os.path.join(self.Path, *pathComponents), False      # False indicates that it is NOT ok for the caller to delete the file after decompressing it, to save space
612
613    def _RemoveExistingDatasetsFromList(self, pathComponents, datasets, progressReporter):
614        self.DatasetType._RemoveExistingDatasetsFromList(os.path.join(self.Path, *pathComponents), datasets, progressReporter)
615
616    def _ImportDatasetsToPath(self, pathComponents, sourceDatasets, mode, progressReporter, options):
617        self.DatasetType._ImportDatasetsToPath(os.path.join(self.Path, *pathComponents), sourceDatasets, mode, progressReporter, options)
618
619    # Overridden methods of Database
620
621    def ImportTable(self, destTableName, sourceTable, fields=None, where=None, orderBy=None, rowCount=None, reportProgress=True, rowDescriptionSingular=None, rowDescriptionPlural=None, copiedOIDFieldName=None, allowSafeCoercions=True):
622
623        # First call the base class method to actually do the import.
624
625        table = super(ArcGISWorkspace, self).ImportTable(destTableName, sourceTable, fields, where, orderBy, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural, copiedOIDFieldName, allowSafeCoercions)
626
627        # Now, if the resulting table has a geometry column, check
628        # whether it has a spatial index. If not, create one. We do
629        # this specifically because I have noticed that sometimes for
630        # shapefiles, ArcGIS appears to discard the spatial index that
631        # was requested in the call to CreateFeatureClass_management.
632        # I have not determined whether it is caused by subsequently
633        # adding fields, by adding records, or what.
634
635        if table.GeometryType is not None:
636            gp = GeoprocessorManager.GetWrappedGeoprocessor()
637            d = gp.Describe(table._GetFullPath())
638
639            if hasattr(d, 'HasSpatialIndex') and not d.HasSpatialIndex:
640                if reportProgress:
641                    self._LogInfo(_(u'Adding a spatial index to %(dn)s.') % {u'dn': table.DisplayName})
642                else:
643                    self._LogDebug(_(u'Adding a spatial index to %(dn)s.') % {u'dn': table.DisplayName})
644                   
645                gp.AddSpatialIndex(table._GetFullPath(), 0, 0, 0)
646
647        # Return successfully.
648
649        return table
650
651    def _TableExists(self, tableName):
652        gp = GeoprocessorManager.GetWrappedGeoprocessor()
653        return gp.Exists(os.path.join(self._Path, tableName))
654
655    def _CreateTable(self, tableName, geometryType, spatialReference, geometryFieldName):
656
657        # If the caller did not specify a geometryType, create a
658        # regular table.
659
660        gp = GeoprocessorManager.GetWrappedGeoprocessor()
661        if geometryType is None:
662            gp.CreateTable_management(self._Path, tableName)
663
664        # Otherwise create a feature class.
665
666        else:
667            geometryType = geometryType.upper()
668
669            hasZ = {False: 'DISABLED', True: 'ENABLED'}[geometryType[-3:] == u'25D']
670           
671            if geometryType in [u'POINT', u'POINT25D']:
672                geometryType = u'POINT'
673            elif geometryType in [u'MULTIPOINT', u'MULTIPOINT25D']:
674                geometryType = u'MULTIPOINT'
675            elif geometryType in [u'LINESTRING', u'MULTILINESTRING', u'LINESTRING25D', u'MULTILINESTRING25D']:
676                geometryType = u'POLYLINE'
677            elif geometryType in [u'POLYGON', u'MULTIPOLYGON', u'POLYGON25D', u'MULTIPOLYGON25D']:
678                geometryType = u'POLYGON'
679
680            srString = Dataset.ConvertSpatialReference('Obj', spatialReference, 'ArcGIS')
681
682            gp.CreateFeatureClass_management(self._Path, tableName, geometryType, None, 'DISABLED', hasZ, srString, None, 0, 0, 0)
683
684        # Return an ArcGISTable instance for the new table.
685
686        return ArcGISTable(os.path.join(self._Path, tableName))
687
688    def _DeleteTable(self, tableName):
689        gp = GeoprocessorManager.GetWrappedGeoprocessor()
690        return gp.Delete_management(os.path.join(self._Path, tableName))
691   
692
693class ArcGISRaster(DatasetCollection):
694    __doc__ = DynamicDocString()
695
696    def _GetPath(self):
697        return self._Path
698
699    Path = property(_GetPath, doc=DynamicDocString())
700
701    def _GetDecompressedFileToReturn(self):
702        return self._DecompressedFileToReturn
703
704    DecompressedFileToReturn = property(_GetDecompressedFileToReturn, doc=DynamicDocString())
705
706    def _GetArcGISDataType(self):
707        return self.GetLazyPropertyValue('ArcGISDataType')
708
709    ArcGISDataType = property(_GetArcGISDataType, doc=DynamicDocString())
710
711    def __init__(self, path, decompressedFileToReturn=None, parentCollection=None, queryableAttributeValues=None, lazyPropertyValues=None, cacheDirectory=None):
712        # TODO: self.__doc__.Obj.ValidateMethodInvocation()
713
714        # Initialize our properties.
715
716        self._Path = path
717        self._DecompressedFileToReturn = decompressedFileToReturn
718        self._GDALDataset = None
719        self._TempCopy = None
720       
721        if parentCollection is None:
722            self._DisplayName = _(u'ArcGIS raster "%(path)s"') % {u'path': path}
723        else:
724            self._DisplayName = _(u'ArcGIS raster "%(path)s"') % {u'path': os.path.join(parentCollection.Path, path)}
725
726        # We allow querying for Grid datasets by band number. If the
727        # parent collection(s) did not define the Band queryable
728        # attribute, we must define it.
729        #
730        # TODO: Add name attribute, derived from describe's BaseName property?
731
732        queryableAttributes = None
733        if parentCollection is not None:
734            bandAttribute = parentCollection.GetQueryableAttribute(u'Band')
735        if parentCollection is None or bandAttribute is None:
736            bandAttribute = QueryableAttribute(u'Band', _(u'Band number'), IntegerTypeMetadata(minValue=1))
737            queryableAttributes = (bandAttribute,)
738
739        # Initialize the base class.
740       
741        super(ArcGISRaster, self).__init__(parentCollection, queryableAttributes, queryableAttributeValues, lazyPropertyValues, cacheDirectory)
742
743        # Validate that the caller has not assigned a value to the
744        # Band queryable attribute, either directly to us or to our
745        # parent collection(s).
746
747        if self.GetQueryableAttributeValue(u'Band') is not None:
748            raise ValueError(_(u'This ArcGISRaster or its parent collection(s) specify a value for the Band queryable attribute. This is not allowed, as the value of that queryable attribute is assigned by the ArcGISRasterBand class.'))
749
750    def _GetDisplayName(self):
751        if self._GDALDataset is not None:
752            return self._GDALDataset.DisplayName
753        return self._DisplayName
754
755    def _GetLazyPropertyPhysicalValue(self, name):
756
757        # If this is a lazy property that we know how to retrieve, get
758        # it from the geoprocessor's Describe object.
759
760        if name not in ['ArcGISDataType', 'Bands', 'SpatialReference']:
761            return None
762
763        if self.ParentCollection is None:
764            path = self._Path
765        else:
766            path = os.path.join(self.ParentCollection.Path, self._Path)
767
768        gp = GeoprocessorManager.GetWrappedGeoprocessor()
769       
770        if not gp.Exists(path):
771            raise ValueError(_(u'Failed to open ArcGIS raster "%(path)s". ArcGIS reports that it does not exist.') % {u'path': path})
772
773        d = gp.Describe(path)
774        if d.DataType.lower() not in [u'rasterdataset', u'rasterlayer']:
775            raise ValueError(_(u'Failed to open "%(path)s" as an ArcGIS raster. ArcGIS reports that it is a %(dt)s, which cannot be opened as a raster.') % {u'path': path, u'dt': d.DataType})
776       
777        self._DisplayName = _(u'ArcGIS %(dt)s "%(path)s"') % {u'dt': d.DataType, u'path': path}
778
779        self.SetLazyPropertyValue('ArcGISDataType', d.DataType)
780        self.SetLazyPropertyValue('Bands', d.BandCount)
781        self.SetLazyPropertyValue('SpatialReference', Dataset.ConvertSpatialReference('arcgis', gp.CreateSpatialReference_management(d.SpatialReference).split(';')[0], 'obj'))
782
783        # Log a debug message with the properties of the raster.
784       
785        self._LogDebug(_(u'%(class)s 0x%(id)08X: Retrieved lazy properties of %(dn)s: Bands=%(Bands)s, SpatialReference=%(SpatialReference)s.'),
786                       {u'class': self.__class__.__name__,
787                        u'id': id(self),
788                        u'dn': self.DisplayName,
789                        u'Bands': repr(self.GetLazyPropertyValue('Bands')),
790                        u'SpatialReference': repr(Dataset.ConvertSpatialReference('obj', self.GetLazyPropertyValue('SpatialReference'), 'arcgis'))})
791
792        # Return the value of the requested property.
793
794        return self.GetLazyPropertyValue(name)
795
796    def _QueryDatasets(self, parsedExpression, progressReporter, options, parentAttrValues):
797
798        # Go through the bands of this dataset, testing whether each
799        # one matches the query expression. For each match, construct
800        # either a GDALRasterBand or an ArcGISRasterBand and add it to
801        # our list of datasets to return.
802
803        datasetsFound = []
804        triedGDAL = False
805        gdalDataset = None
806
807        for band in range(1, self.GetLazyPropertyValue('Bands') + 1):
808            if parsedExpression is not None:
809                attrValues = {u'Band': band}
810                attrValues.update(parentAttrValues)
811                try:
812                    result = parsedExpression.eval(attrValues)
813                except Exception, e:
814                    continue        # TODO: report better message
815            else:
816                result = True
817
818            if result is None or result:
819                self._LogDebug(_(u'%(class)s 0x%(id)08X: Query result for band %(band)i: %(result)s'), {u'class': self.__class__.__name__, u'id': id(self), u'band': band, u'result': repr(result)})
820
821            if result:
822                if not triedGDAL:
823                    gdalDataset = self._TryToInstantiateGDALDataset()
824                    triedGDAL = True
825
826                if gdalDataset is not None:
827                    datasetsFound.append(GDALRasterBand(gdalDataset, band))
828                else:
829                    datasetsFound.append(ArcGISRasterBand(self, band))
830               
831                if progressReporter is not None:
832                    progressReporter.ReportProgress()
833
834        return datasetsFound
835
836    def _TryToInstantiateGDALDataset(self):
837       
838        # If we already instantiated a GDALDataset for this raster,
839        # return it.
840
841        if self._GDALDataset is not None:
842            return self._GDALDataset
843
844        # Before we try to instantiate a GDALDataset for this raster,
845        # retrieve the SpatialReference lazy property. Unfortunately,
846        # GDAL does not know how to recognize some of the
847        # ESRI-specific WKT strings that ArcGIS stores in rasters. To
848        # work around this, we retrieve it using the geoprocessor.
849        # Next, when we instantiate the GDALDataset, we will provide
850        # it as a lazy property so that GDALDataset does not try to
851        # retrieve it.
852
853        self.GetLazyPropertyValue('SpatialReference')       # Do not do anything with it. We just need it to be stored in the self._LazyPropertyValues dictionary.
854
855        # If we created an IMG file for it in a temporary cache
856        # directory, instantate and return a GDALDataset for it.
857
858        if self._TempCopy is not None:
859            self._GDALDataset = GDALDataset(self._TempCopy, parentCollection=self.ParentCollection, queryableAttributeValues=self._QueryableAttributeValues, lazyPropertyValues=self._LazyPropertyValues)
860            self._RegisterForCloseAtExit()
861            return self._GDALDataset
862
863        # We have not instantiated a GDALDataset for this raster and
864        # did not make a temporary IMG file. Determine if the path
865        # resolves to a file system object that is not in a file
866        # geodatabase. If so, instantiate and return a GDALDataset for
867        # it.
868
869        if self.ParentCollection is None:
870            path = self._Path
871        else:
872            path = os.path.join(self.ParentCollection.Path, self._Path)
873
874        if os.path.exists(path) and len(os.path.dirname(path)) > 0 and GeoprocessorManager.GetWrappedGeoprocessor().Describe(os.path.dirname(path)).DataType.lower() == u'folder':
875            self._GDALDataset = GDALDataset(path, decompressedFileToReturn=self.DecompressedFileToReturn, parentCollection=self.ParentCollection, queryableAttributeValues=self._QueryableAttributeValues, lazyPropertyValues=self._LazyPropertyValues, cacheDirectory=self.CacheDirectory)
876            self._RegisterForCloseAtExit()
877            return self._GDALDataset
878
879        # The path does not resolve to a file system object that is
880        # not in a file geodatabase. Therefore, it must be an
881        # in-memory raster layer or a raster in a geodatabase, and we
882        # cannot access it directly with a GDALDataset.
883
884        return None
885
886    def _InstantiateGDALDataset(self):
887
888        # First try to open the raster directly with GDAL.
889
890        if self._TryToInstantiateGDALDataset() is not None:
891            return self._GDALDataset
892
893        # We failed to open raster directly with GDAL, probably
894        # because it is in a geodatabase or is an in-memory raster
895        # layer. Create a temporary cache directory and copy the
896        # raster into it as an IMG file.
897        #
898        # TODO: This design is less than optimal if the raster has
899        # multiple bands and the caller is not interested in all of
900        # them. Investigate whether it is possible to copy just a
901        # single band with the ArcGIS CopyRaster_management tool. If
902        # so, consider changing the design so that ArcGISRasterBand
903        # creates temporary copies of individual bands.
904
905        if self.ParentCollection is None:
906            path = self._Path
907        else:
908            path = os.path.join(self.ParentCollection.Path, self._Path)
909
910        tempDir = self._CreateTempDirectory()
911        tempPath = os.path.join(tempDir, 'raster.img')
912
913        self._LogDebug(_(u'%(class)s 0x%(id)08X: Copying %(dn)s to "%(temp)s".'), {u'class': self.__class__.__name__, u'id': id(self), u'dn': self.DisplayName, u'temp': tempPath})
914        GeoprocessorManager.GetWrappedGeoprocessor().CopyRaster_management(path, tempPath)
915
916        self._TempCopy = tempPath
917
918        # Now try to open it again. This should succeed.
919
920        if self._TryToInstantiateGDALDataset() is not None:
921            return self._GDALDataset
922
923    def _Close(self):
924        if hasattr(self, '_GDALDataset') and self._GDALDataset is not None:
925            self._GDALDataset.Close()
926            self._GDALDataset = None
927        super(ArcGISRaster, self)._Close()
928
929    @classmethod
930    def _RemoveExistingDatasetsFromList(cls, path, datasets, progressReporter):
931
932        # Because ArcGIS does not support adding bands to existing
933        # rasters, we cannot implement 'add' mode in the way that most
934        # people would expect (that is, if len(datasets) is greater
935        # than the number of existing bands, add the remaining
936        # datasets as new bands). Instead we assume that if the raster
937        # exists, it already has all of the necessary bands. Thus, if
938        # it exists, remove all of the datasets from the caller's
939        # list.
940
941        numDatasets = len(datasets)
942
943        if cls._ArcGISRasterExists(path):
944            cls._LogDebug(_(u'%(class)s: ArcGIS raster "%(path)s" exists.'), {u'class': cls.__name__, u'path': path})
945            while len(datasets) > 0:
946                del datasets[0]
947        else:
948            cls._LogDebug(_(u'%(class)s: ArcGIS raster "%(path)s" does not exist.'), {u'class': cls.__name__, u'path': path})
949
950        # Report that we checked all of these datasets.
951
952        if progressReporter is not None:
953            progressReporter.ReportProgress(numDatasets)
954
955    @classmethod
956    def _ArcGISRasterExists(cls, name):
957
958        # Check whether name is a file system path. If it is,
959        # determine its existence using the file system. This is
960        # faster than using the geoprocessor.
961
962        if (name[0] in ['/', '\\'] or hasattr(os.path, 'splitdrive') and os.path.splitdrive(name)[0] != '' or hasattr(os.path, 'splitunc') and os.path.splitunc(name)[0] != '') and os.path.splitext(os.path.dirname(name))[1].lower() != '.gdb':
963            return os.path.exists(name)
964
965        # name is not a file system path. Determine its existence
966        # using the geoprocessor.
967       
968        return GeoprocessorManager.GetWrappedGeoprocessor().Exists(name)
969
970    @classmethod
971    def _ImportDatasetsToPath(cls, path, sourceDatasets, mode, progressReporter, options):
972
973        # Unpack the options dictionary.
974
975        useUnscaledData = 'useUnscaledData' in options and options['useUnscaledData']
976        calculateStatistics = 'calculateStatistics' not in options or options['calculateStatistics']        # Note that default for calculateStatistics is True.
977        buildRAT = 'buildRAT' in options and options['buildRAT']
978        buildPyramids = 'buildPyramids' in options and options['buildPyramids']                             # TODO: Should we just read the environment?
979
980        if 'blockSize' in options:
981            blockSize = options['blockSize']
982        else:
983            blockSize = None
984
985        # Validate that the source datasets are all Grids and have
986        # dimensions 'yx', evenly-spaced coordinates, valid data
987        # types, and the same spatial reference, shape, coordinate
988        # increments, corner coordinates, and data type.
989
990        gdal = cls._gdal()
991        gdal.ErrorReset()
992
993        if not hasattr(ArcGISRaster, '_GDALDataTypeForNumpyDataType'):
994            ArcGISRaster._GDALDataTypeForNumpyDataType = {u'uint8': gdal.GDT_Byte,
995                                                          u'int8': gdal.GDT_Byte,
996                                                          u'uint16': gdal.GDT_UInt16,
997                                                          u'int16': gdal.GDT_Int16,
998                                                          u'uint32': gdal.GDT_UInt32,
999                                                          u'int32': gdal.GDT_Int32,
1000                                                          u'float32': gdal.GDT_Float32,
1001                                                          u'float64': gdal.GDT_Float64,
1002                                                          u'complex32': gdal.GDT_CFloat32,
1003                                                          u'complex64': gdal.GDT_CFloat64}
1004
1005        for dataset in sourceDatasets:
1006            if not isinstance(dataset, Grid):
1007                raise TypeError(_(u'Cannot import %(dn)s into ArcGIS raster "%(path)s" because it is a %(type)s, which is not a Grid. It must be a Grid to be imported into an ArcGIS raster.') % {u'dn': dataset.DisplayName, u'path': path, u'type': dataset.__class__.__name__})
1008            if dataset.Dimensions != u'yx':
1009                raise ValueError(_(u'Cannot import %(dn)s into ArcGIS raster "%(path)s" because it has dimensions "%(dim)s". It must have dimensions "yx" to be imported into an ArcGIS raster.') % {u'dn': dataset.DisplayName, u'path': path, u'dim': dataset.Dimensions})
1010            if dataset.CoordDependencies != (None, None):
1011                raise ValueError(_(u'Cannot import %(dn)s into ArcGIS raster "%(path)s" because it does not have evenly-spaced coordinates (the coordinate dependencies are %(deps)s). It must have evenly-spaced coordinates (coordinate dependencies of (None, None)) to be imported into an ArcGIS raster.') % {u'dn': dataset.DisplayName, u'path': path, u'deps': repr(dataset.CoordDependencies)})
1012            if useUnscaledData:
1013                if dataset.UnscaledDataType not in ArcGISRaster._GDALDataTypeForNumpyDataType.keys():
1014                    raise ValueError(_(u'Cannot import %(dn)s into ArcGIS raster "%(path)s" because it has the unscaled data type %(dt)s. To be imported into an ArcGIS raster, it must have one of the following unscaled data types: %(dts)s.') % {u'dn': dataset.DisplayName, u'path': path, u'dt': dataset.UnscaledDataType, u'dts': u', '.join(ArcGISRaster._GDALDataTypeForNumpyDataType.keys())})
1015            elif dataset.DataType not in ArcGISRaster._GDALDataTypeForNumpyDataType.keys():
1016                raise ValueError(_(u'Cannot import %(dn)s into ArcGIS raster "%(path)s" because it has the data type %(dt)s. To be imported into an ArcGIS raster, it must have one of the following data types: %(dts)s.') % {u'dn': dataset.DisplayName, u'path': path, u'dt': dataset.DataType, u'dts': u', '.join(ArcGISRaster._GDALDataTypeForNumpyDataType.keys())})
1017
1018        for i in range(1, len(sourceDatasets)):
1019            if not sourceDatasets[0].GetSpatialReference('obj').IsSame(sourceDatasets[i].GetSpatialReference('obj')):
1020                raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different spatial references (%(sr1)s and %(sr2)s). This function requires that all of the bands of an ArcGIS raster have the same spatial reference.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'sr1': repr(sourceDatasets[0].GetSpatialReference('wkt')), u'sr2': repr(sourceDatasets[i].GetSpatialReference('wkt'))})
1021            if sourceDatasets[0].Shape != sourceDatasets[i].Shape:
1022                raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different shapes (%(shape1)s and %(shape2)s). This function requires that all of the bands of an ArcGIS raster have the same shape.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'shape1': repr(sourceDatasets[0].Shape), u'shape2': repr(sourceDatasets[i].Shape)})
1023            if sourceDatasets[0].CoordIncrements != sourceDatasets[i].CoordIncrements:
1024                raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different coordinate increments (%(incr1)s and %(incr2)s). This function requires that all of the bands of an ArcGIS raster have the same coordinate increments.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'incr1': repr(sourceDatasets[0].CoordIncrements), u'incr2': repr(sourceDatasets[i].CoordIncrements)})
1025            if sourceDatasets[0].MinCoords['x',0] != sourceDatasets[i].MinCoords['x',0]:
1026                raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different minimum x coordinates (%(c1)s and %(c2)s). This function requires that all of the bands of an ArcGIS raster have the same corner coordinates.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'c1': repr(sourceDatasets[0].MinCoords['x',0]), u'c2': repr(sourceDatasets[i].MinCoords['x',0])})
1027            if sourceDatasets[0].MinCoords['y',0] != sourceDatasets[i].MinCoords['y',0]:
1028                raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different minimum y coordinates (%(c1)s and %(c2)s). This function requires that all of the bands of an ArcGIS raster have the same corner coordinates.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'c1': repr(sourceDatasets[0].MinCoords['y',0]), u'c2': repr(sourceDatasets[i].MinCoords['y',0])})
1029            if useUnscaledData:
1030                if sourceDatasets[0].UnscaledDataType != sourceDatasets[i].UnscaledDataType:
1031                    raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different unscaled data types (%(dt1)s and %(dt2)s). This function requires that all of the bands of an ArcGIS raster have the same data type.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'dt1': sourceDatasets[0].UnscaledDataType, u'dt2': sourceDatasets[i].UnscaledDataType})
1032            elif sourceDatasets[0].DataType != sourceDatasets[i].DataType:
1033                raise ValueError(_(u'Cannot import both %(dn1)s and %(dn2)s into ArcGIS raster "%(path)s" because the two source datasets have different data types (%(dt1)s and %(dt2)s). This function requires that all of the bands of an ArcGIS raster the same data type.') % {u'dn1': sourceDatasets[0].DisplayName, u'dn2': sourceDatasets[i].DisplayName, u'path': path, u'dt1': sourceDatasets[0].DataType, u'dt2': sourceDatasets[i].DataType})
1034
1035        # If the mode is 'replace' and the raster exists, delete it.
1036
1037        gp = GeoprocessorManager.GetWrappedGeoprocessor()
1038
1039        if mode == u'replace' and cls._ArcGISRasterExists(path):
1040            cls._LogDebug(_(u'%(class)s: Deleting existing ArcGIS raster "%(path)s".'), {u'class': cls.__name__, u'path': path})
1041            try:
1042                gp.Delete_management(path)
1043            except Exception, e:
1044                raise RuntimeError(_(u'Failed to delete the existing ArcGIS raster "%(path)s" due to %(e)s: %(msg)s') % {u'path': path, u'e': e.__class__.__name__, u'msg': cls._Unicode(e)})
1045
1046        # Otherwise, if the path is a file system path, create the
1047        # parent directories, if they do not exist already. Use
1048        # ArcGIS's CreateFolder_management tool, so the ArcGIS catalog
1049        # is aware of the directories.
1050
1051        elif (path[0] in ['/', '\\'] or hasattr(os.path, 'splitdrive') and os.path.splitdrive(path)[0] != '' or hasattr(os.path, 'splitunc') and os.path.splitunc(path)[0] != '') and not os.path.exists(os.path.dirname(path)):
1052            cls._LogDebug(_(u'%(class)s: Creating directory "%(path)s".'), {u'class': cls.__name__, u'path': os.path.dirname(path)})
1053
1054            if hasattr(os.path, 'splitdrive') and os.path.splitdrive(path)[0] != '':
1055                root, subdirs = os.path.splitdrive(os.path.dirname(path))
1056                root = root + '\\'
1057            elif hasattr(os.path, 'splitunc') and os.path.splitunc(path)[0] != '':
1058                root, subdirs = os.path.splitunc(os.path.dirname(path))
1059            else:
1060                root, subdirs = path[0], os.path.dirname(path)[1:]
1061
1062            subdirsList = []
1063            while len(subdirs) > 1:
1064                subdirsList.insert(0, os.path.basename(subdirs))
1065                subdirs = os.path.dirname(subdirs)
1066
1067            dirToCheck = root
1068            for subdir in subdirsList:
1069                if not os.path.isdir(os.path.join(dirToCheck, subdir)):
1070                    try:
1071                        gp.CreateFolder_management(dirToCheck, subdir)
1072                    except Exception, e:
1073                        raise RuntimeError(_(u'Failed to create the directory "%(path)s" due to %(e)s: %(msg)s') % {u'path': os.path.join(dirToCheck, subdir), u'e': e.__class__.__name__, u'msg': cls._Unicode(e)})
1074                dirToCheck = os.path.join(dirToCheck, subdir)
1075
1076        # If the output format is ArcInfo binary grid, verify that the
1077        # raster name conforms to ArcGIS's rules and if not, raise a
1078        # helpful error message. (Many users make mistakes with binary
1079        # grid names but cannot tell what went wrong from ArcGIS's
1080        # unhelpful error messages.)
1081
1082        outputIsFile = os.path.isdir(os.path.dirname(path)) and os.path.splitext(os.path.dirname(path))[1].lower() != '.gdb'
1083        outputIsAIG = outputIsFile and u'.' not in os.path.basename(path)
1084        if outputIsAIG:
1085            rasterName = os.path.basename(path)
1086            if u' ' in rasterName:
1087                raise ValueError(_(u'Cannot create an ArcGIS raster named "%(path)s" because the raster\'s name contains a space. The names of rasters in ArcInfo Binary Grid format cannot contain spaces.') % {u'path': path})
1088            if rasterName[0] >= u'0' and rasterName[0] <= u'9':
1089                raise ValueError(_(u'Cannot create an ArcGIS raster named "%(path)s" because the raster\'s name starts with a number. The names of rasters in ArcInfo Binary Grid format cannot start with numbers.') % {u'path': path})
1090            if len(sourceDatasets) > 1:
1091                if len(rasterName) > 9:
1092                    raise ValueError(_(u'Cannot create an ArcGIS grid stack named "%(path)s" because the grid stack\'s name is longer than nine characters. The names of ArcGIS grid stacks must be no longer than nine characters.') % {u'path': path})
1093            elif len(rasterName) > 13:
1094                raise ValueError(_(u'Cannot create an ArcGIS raster named "%(path)s" because the raster\'s name is longer than thirteen characters. The names of rasters in ArcInfo Binary Grid format must be no longer than thirteen characters.') % {u'path': path})
1095
1096        # At this point, the raster should not exist. If the
1097        # destination is the file system (rather than a geodatabase)
1098        # and the output format one that GDAL can create, use GDAL to
1099        # create the raster directly at the destination path. This
1100        # will be much faster than calling any geoprocessor functions.
1101        #
1102        # Do not use GDAL to calculate statistics or build pyramids.
1103        # Even though GDAL can do it faster than the geoprocessor, we
1104        # prefer to use the geoprocessor to maximize the compatibility
1105        # of the output raster with ArcGIS. For example, I do not know
1106        # how to get GDAL to build an ArcGIS-compatible histogram.
1107        # Without the histogram ArcGIS defaults to displaying integer
1108        # rasters using a black-to-white stretch rather than a unique
1109        # value classifier with random colors. ArcGIS users will
1110        # expect colors; to get the necessary histogram, we have to
1111        # calculate statistics with the geoprocessor.
1112
1113        if outputIsFile and not outputIsAIG and os.path.splitext(path)[1].lower() not in [u'.asc', u'.gif', u'.j2c', u'.j2k', u'.jp2', u'.jpc', u'.jpe', u'.jpg', u'.jpeg', u'.jpx', u'.png', u'.txt']:
1114            cls._LogDebug(_(u'%(class)s: Creating ArcGIS raster "%(path)s" with GDAL.'), {u'class': cls.__name__, u'path': path})
1115            GDALDataset._ImportDatasetsToPath(path, sourceDatasets, mode, None, {'useArcGISSpatialReference': True, 'useUnscaledData': useUnscaledData, 'calculateStatistics': False, 'blockSize': blockSize})
1116            gp.RefreshCatalog(os.path.dirname(path))
1117           
1118        # Otherwise, create a temporary directory, write an IMG file
1119        # to it with GDAL, and copy the IMG file to the destination
1120        # path.
1121
1122        else:
1123            cls._LogDebug(_(u'%(class)s: Creating ArcGIS raster "%(path)s" by creating a temporary IMG file and then copying it with the geoprocessor.'), {u'class': cls.__name__, u'path': path})
1124
1125            # Create a temporary directory.
1126
1127            import tempfile, shutil
1128            tempDir = tempfile.mkdtemp(prefix='GeoEco_' + cls.__name__ + '_Temp_')
1129            cls._LogDebug(_(u'%(class)s: Created temporary directory %(dir)s.'), {u'class': cls.__name__, u'dir': tempDir})
1130
1131            try:
1132                # Write the IMG file to the temporary directory.
1133
1134                tempRaster = os.path.join(tempDir, 'raster.img')
1135                GDALDataset._ImportDatasetsToPath(tempRaster, sourceDatasets, mode, None, {'useArcGISSpatialReference': True, 'useUnscaledData': useUnscaledData, 'calculateStatistics': False, 'blockSize': blockSize})
1136                gp.RefreshCatalog(tempDir)
1137
1138                # If the destination raster is an ArcInfo ASCII grid,
1139                # use the RasterToASCII_conversion tool to create it.
1140
1141                if os.path.splitext(path)[1].lower() in ['.asc', '.txt']:
1142                    cls._LogDebug(_(u'%(class)s: Converting "%(src)s" to "%(dest)s".'), {u'class': cls.__name__, u'src': tempRaster, u'dest': path})
1143                    gp.RasterToASCII_conversion(tempRaster, path)
1144
1145                # Otherwise we'll use CopyRaster_management.
1146
1147                else:
1148
1149                    # If the destination raster is an ArcInfo binary grid
1150                    # and copying the raster to that directory will cause
1151                    # ArcGIS to crash due to a bug, create temporary
1152                    # ArcInfo binary grids that will prevent the crash.
1153
1154                    if outputIsAIG:
1155                        tempRasters = cls._CreateTemporaryRastersToPreventArcGISCrash(os.path.dirname(path), 1)
1156
1157                    # Copy the raster to the destination path. Due to the
1158                    # problems described in tickets #310 and #311, call
1159                    # Copy_mangement on ArcGIS 9.1, and
1160                    # CopyRaster_management on later versions.
1161
1162                    try:
1163                        cls._LogDebug(_(u'%(class)s: Copying "%(src)s" to "%(dest)s".'), {u'class': cls.__name__, u'src': tempRaster, u'dest': path})
1164                        if GeoprocessorManager.GetArcGISMajorVersion() <= 9 and GeoprocessorManager.GetArcGISMinorVersion() <= 1:
1165                            gp.Copy_Management(tempRaster, path)
1166                        else:
1167                            gp.CopyRaster_Management(tempRaster, path)
1168
1169                    # If we created temporary rasters to prevent an ArcGIS
1170                    # crash, delete them now.
1171
1172                    finally:
1173                        if outputIsAIG:
1174                            cls._DeleteTemporaryRastersThatPreventedArcGISCrash(tempRasters)
1175
1176            # Delete the temporary directory.
1177
1178            finally:
1179                cls._LogDebug(_(u'%(class)s: Deleting temporary directory %(dir)s.'), {u'class': cls.__name__, u'dir': tempDir})
1180                shutil.rmtree(tempDir, onerror=DatasetCollection._LogFailedRemoval)
1181
1182        # The raster now exists at the final destination. If any of
1183        # the additional processing steps fail, delete the raster.
1184
1185        try:
1186            # Calculate statistics.
1187
1188            if calculateStatistics:
1189                cls._LogDebug(_(u'%(class)s: Calculating statistics for ArcGIS raster "%(path)s".'), {u'class': cls.__name__, u'path': path})
1190                gp.CalculateStatistics_management(path)
1191
1192            # Build the raster attribute table. This can only be done for
1193            # single-band integer rasters.
1194
1195            if buildRAT and len(sourceDatasets) == 1 and (useUnscaledData and sourceDatasets[0].UnscaledDataType in [u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32'] or not useUnscaledData and sourceDatasets[0].DataType in [u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32']):
1196                cls._LogDebug(_(u'%(class)s: Building a raster attribute table for ArcGIS raster "%(path)s".'), {u'class': cls.__name__, u'path': path})
1197                gp.BuildRasterAttributeTable_management(path)
1198
1199            # Build pyramids.
1200
1201            if buildPyramids:
1202                cls._LogDebug(_(u'%(class)s: Building pyramids for ArcGIS raster "%(path)s".'), {u'class': cls.__name__, u'path': path})
1203                gp.BuildPyramids_management(path)
1204               
1205        except:
1206            cls._LogDebug(_(u'%(class)s: Deleting partially-created ArcGIS raster "%(path)s" because an error was raised during creation.'), {u'class': cls.__name__, u'path': path})
1207            try:
1208                gp.Delete_management(path)
1209            except Exception, e:
1210                cls._LogWarning(_(u'Failed to delete the partially-created ArcGIS raster "%(path)s" due to %(e)s: %(msg)s') % {u'path': path, u'e': e.__class__.__name__, u'msg': cls._Unicode(e)})
1211            raise
1212
1213        # Report progress.
1214
1215        if progressReporter is not None:
1216            progressReporter.ReportProgress()
1217
1218    @classmethod
1219    def _CreateTemporaryRastersToPreventArcGISCrash(cls, directory, rastersToAdd):
1220
1221        # See http://forums.esri.com/Thread.asp?c=93&f=1729&t=196716&mc=0 for a
1222        # complete description of the problem that this function is designed to
1223        # work around.
1224
1225        # First determine the maximum entry in the arc.dir file.
1226
1227        import glob
1228       
1229        infoEntries = glob.glob(os.path.join(directory, "info", "arc[0-9][0-9][0-9][0-9].*"))
1230        if len(infoEntries) <= 0:
1231            return []
1232       
1233        infoEntries.sort()
1234        maxInfoEntry = int(os.path.basename(infoEntries[len(infoEntries)-1])[3:7])
1235
1236        # If adding rastersToAdd integer rasters to the directory
1237        # would not cause the maximum entry to cross a multiple of
1238        # 1024, then it would not cause Arc to crash, and we can just
1239        # return.
1240       
1241        maxInfoEntry = divmod(maxInfoEntry, 1024)[1]
1242        if maxInfoEntry + rastersToAdd * 3 < 1023:          # Each integer raster requires 3 entries in arc.dir (floating point rasters only require 2)
1243            return []
1244
1245        # If the current entry is only 1 less than the 1024 boundary
1246        # (actually 1 less than 1023 since the entries are 0-based),
1247        # we can just return because all rasters require at least two
1248        # entries, so it is guaranteed that we would not hit the magic
1249        # crashing number (which is a multiple of 1024 - 1, again
1250        # since the entries are 0-based).
1251
1252        if maxInfoEntry == 1023 - 1:
1253            return []
1254
1255        # We're going to cross a multiple of 1024 and there could be a
1256        # crash. Create enough temporary rasters to safely take us
1257        # just over the boundary.
1258
1259        import GeoEco
1260
1261        cls._LogDebug(_(u'%(class)s: Creating temporary rasters in %(dir)s to prevent ArcGIS from crashing when %(rasters)i ArcInfo binary grids are added to that directory. The temporary rasters begin with \"tmp\" and should be deleted automatically after the real rasters are added. If they are not, you can delete them manually.') % {u'class': cls.__name__, u'dir' : directory, u'rasters' : rastersToAdd})
1262
1263        entriesNeeded = 1024 - maxInfoEntry
1264        rastersCreated = []
1265        gp = GeoprocessorManager.GetWrappedGeoprocessor()
1266
1267        while entriesNeeded > 0:
1268            raster = None
1269            while raster is None or os.path.exists(raster):
1270                raster = os.path.join(directory, "tmp%08X" % random.randint(0, sys.maxint))
1271
1272            # If creating an integer raster would not crash Arc, do
1273            # it. Otherwise create a floating-point raster. Due to the
1274            # problems described in tickets #310 and #311, call
1275            # Copy_mangement on ArcGIS 9.1 and 9.2, and
1276            # CopyRaster_management on ArcGIS 9.3.
1277
1278            if entriesNeeded != 4:
1279                dummyRaster = os.path.join(os.path.dirname(sys.modules[u'GeoEco'].__file__), u'ArcGISToolbox', u'Rasters', u'dummyint')
1280                entriesNeeded -= 3
1281            else:
1282                dummyRaster = os.path.join(os.path.dirname(sys.modules[u'GeoEco'].__file__), u'ArcGISToolbox', u'Rasters', u'dummyfloat')
1283                entriesNeeded -= 2
1284
1285            if GeoprocessorManager.GetArcGISMajorVersion() <= 9 and GeoprocessorManager.GetArcGISMinorVersion() <= 2:
1286                gp.Copy_Management(dummyRaster, raster)
1287            else:
1288                gp.CopyRaster_Management(dummyRaster, raster)
1289
1290            rastersCreated.append(raster)
1291
1292        cls._LogDebug(_(u'%(class)s: %(count)i temporary rasters created.') % {u'class': cls.__name__, u'count': len(rastersCreated)})
1293
1294        # Return the list of rasters we created.
1295
1296        return rastersCreated
1297
1298    @classmethod
1299    def _DeleteTemporaryRastersThatPreventedArcGISCrash(cls, rasters):
1300        if len(rasters) > 0:
1301            cls._LogDebug(_(u'%(class)s: Deleting temporary rasters from %(dir)s.') % {u'class': cls.__name__, u'dir' : directory})
1302            gp = GeoprocessorManager.GetWrappedGeoprocessor()
1303            for raster in rasters:
1304                gp.Delete(raster)
1305
1306
1307class ArcGISRasterBand(Grid):
1308    __doc__ = DynamicDocString()
1309
1310    def _GetBand(self):
1311        return self._Band
1312
1313    Band = property(_GetBand, doc=DynamicDocString())
1314
1315    def __init__(self, arcGISRaster, band, queryableAttributeValues=None, lazyPropertyValues=None):
1316        # TODO: self.__doc__.Obj.ValidateMethodInvocation()
1317
1318        # Initialize our properties.
1319
1320        self._Band = band
1321        self._NoDataValueRetrievalFailed = False
1322
1323        # Assign values to known queryable attributes and lazy
1324        # properties.
1325
1326        qav = {}
1327        if queryableAttributeValues is not None:
1328            qav.update(queryableAttributeValues)
1329
1330        qav[u'Band'] = band
1331
1332        lpv = {}
1333        if lazyPropertyValues is not None:
1334            lpv.update(lazyPropertyValues)
1335
1336        lpv['Dimensions'] = u'yx'
1337        lpv['PhysicalDimensions'] = u'yx'
1338        lpv['PhysicalDimensionsFlipped'] = (True, False)       # TODO: This is correct when we read the raster with GDAL, which we do with ArcGIS 9.x, but with ArcGIS 10.x we may ultimately read it with Arc's own numpy API. What does it do?
1339        lpv['CoordDependencies'] = (None, None)
1340       
1341        if arcGISRaster.GetLazyPropertyValue('TIncrementUnit', False) is None:
1342            lpv['TIncrementUnit'] = None
1343           
1344        if arcGISRaster.GetLazyPropertyValue('TSemiRegularity', False) is None:
1345            lpv['TSemiRegularity'] = None
1346           
1347        if arcGISRaster.GetLazyPropertyValue('TCountPerSemiRegularPeriod', False) is None:
1348            lpv['TCountPerSemiRegularPeriod'] = None
1349           
1350        if arcGISRaster.GetLazyPropertyValue('TCornerCoordType', False) is None:
1351            lpv['TCornerCoordType'] = None
1352
1353        # Initialize the base class.
1354
1355        super(ArcGISRasterBand, self).__init__(arcGISRaster, queryableAttributeValues=qav, lazyPropertyValues=lpv)
1356
1357    def _Close(self):
1358        super(ArcGISRasterBand, self)._Close()
1359        if hasattr(self, 'ParentCollection') and self.ParentCollection is not None:
1360            self.ParentCollection.Close()
1361
1362    def _GetDisplayName(self):
1363        return _(u'band %(band)i of %(dn)s') % {u'band': self._Band, u'dn': self.ParentCollection.DisplayName}
1364
1365    def _GetLazyPropertyPhysicalValue(self, name):
1366
1367        # If it is not a known lazy property, return None.
1368
1369        if name not in ['SpatialReference', 'Shape', 'CoordIncrements', 'CornerCoords', 'UnscaledDataType', 'UnscaledNoDataValue']:
1370            return None
1371
1372        # The spatial reference is maintained by ArcGIS on a
1373        # per-raster basis, not a per-band basis. If the caller wants
1374        # that, retrieve its value from our parent ArcGISRaster.
1375
1376        if name == 'SpatialReference':
1377            return self.ParentCollection.GetLazyPropertyValue(name)
1378
1379        # If the caller wants any of the other properties, get them
1380        # from the geoprocessor's Describe object, unless the caller
1381        # wants the UnscaledNoDataValue and we previously tried to get
1382        # it from the Describe object and failed.
1383
1384        gp = GeoprocessorManager.GetWrappedGeoprocessor()
1385       
1386        if self.ParentCollection.ParentCollection is None:
1387            path = self.ParentCollection.Path
1388        else:
1389            path = os.path.join(self.ParentCollection.ParentCollection.Path, self.ParentCollection.Path)
1390
1391        if self.ParentCollection.GetLazyPropertyValue('Bands') > 1:
1392            path = os.path.join(path, u'Band_' + unicode(self._Band))
1393
1394        if name in ['Shape', 'CoordIncrements', 'CornerCoords', 'UnscaledDataType'] or name == 'UnscaledNoDataValue' and not self._NoDataValueRetrievalFailed:
1395            d = gp.Describe(path)
1396
1397            self.SetLazyPropertyValue('Shape', (d.Height, d.Width))
1398            self.SetLazyPropertyValue('CoordIncrements', (d.MeanCellHeight, d.MeanCellWidth))
1399            self.SetLazyPropertyValue('CornerCoords', (float(d.Extent.split()[1]) + d.MeanCellHeight / 2.0, float(d.Extent.split()[0]) + d.MeanCellWidth / 2.0))
1400
1401            pixelType = d.PixelType.upper()
1402            if pixelType in ['U1', 'U2', 'U4', 'U8', 'UCHAR']:
1403                self.SetLazyPropertyValue('UnscaledDataType', u'uint8')
1404            elif pixelType in ['S8', 'CHAR']:
1405                self.SetLazyPropertyValue('UnscaledDataType', u'int8')
1406            elif pixelType in ['U16', 'USHORT']:
1407                self.SetLazyPropertyValue('UnscaledDataType', u'uint16')
1408            elif pixelType in ['S16', 'SHORT']:
1409                self.SetLazyPropertyValue('UnscaledDataType', u'int16')
1410            elif pixelType in ['U32', 'ULONG']:
1411                self.SetLazyPropertyValue('UnscaledDataType', u'uint32')
1412            elif pixelType in ['S32', 'LONG']:
1413                self.SetLazyPropertyValue('UnscaledDataType', u'int32')
1414            elif pixelType in ['F32', 'FLOAT']:
1415                self.SetLazyPropertyValue('UnscaledDataType', u'float32')
1416            elif pixelType in ['F64', 'DOUBLE']:
1417                self.SetLazyPropertyValue('UnscaledDataType', u'float64')
1418            else:
1419                raise ValueError(_(u'%(dn)s cannot be opened because it has an unknown ArcGIS PixelType "%(pt)s".') % {u'pt': d.PixelType})
1420
1421            # ArcGIS 9.3.1 and possibly other versions sometimes raise
1422            # the following exception when you try to access the
1423            # NoDataValue attribute of a Describe object for a raster:
1424            #
1425            #     RuntimeError: DescribeData: Get attribute NoDataValue does not exist
1426            #
1427            # It can happen whether or not the raster has a NoData
1428            # value. I have discerned no reliable pattern to the
1429            # failure and do not know how to work around it using
1430            # ArcGIS. Instead I extract a small portion of the raster
1431            # to an IMG file and read the NoData value with GDAL. See
1432            # below.
1433
1434            try:
1435                noDataValue = d.NoDataValue
1436            except:
1437                self._LogDebug(_(u'%(class)s 0x%(id)08X: Warning: Failed to retrieve the NoDataValue from the geoprocessor Describe object for %(dn)s. This is a known bug in ArcGIS. If the NoDataValue is needed, it will be obtained exporting part of the raster to a temporary IMG file and reading it with GDAL.'), {u'class': self.__class__.__name__, u'id': id(self), u'dn': self.DisplayName})
1438                self._NoDataValueRetrievalFailed = True
1439            else:
1440                self.SetLazyPropertyValue('UnscaledNoDataValue', noDataValue)
1441
1442            # Log a debug message with the properties of the band.
1443
1444            if not self._NoDataValueRetrievalFailed:
1445                self._LogDebug(_(u'%(class)s 0x%(id)08X: Retrieved lazy properties of %(dn)s: Dimensions=%(Dimensions)s, PhysicalDimensions=%(PhysicalDimensions)s, PhysicalDimensionsFlipped=%(PhysicalDimensionsFlipped)s, Shape=%(Shape)s, CoordIncrements=%(CoordIncrements)s, CornerCoords=%(CornerCoords)s, UnscaledDataType=%(UnscaledDataType)s, UnscaledNoDataValue=%(UnscaledNoDataValue)s.'),
1446                               {u'class': self.__class__.__name__,
1447                                u'id': id(self),
1448                                u'dn': self.DisplayName,
1449                                u'Dimensions': self.GetLazyPropertyValue('Dimensions'),
1450                                u'PhysicalDimensions': self.GetLazyPropertyValue('PhysicalDimensions'),
1451                                u'PhysicalDimensionsFlipped': repr(self.GetLazyPropertyValue('PhysicalDimensionsFlipped')),
1452                                u'Shape': repr(self.GetLazyPropertyValue('Shape')),
1453                                u'CoordIncrements': repr(self.GetLazyPropertyValue('CoordIncrements')),
1454                                u'CornerCoords': self.GetLazyPropertyValue('CornerCoords'),
1455                                u'UnscaledDataType': repr(self.GetLazyPropertyValue('UnscaledDataType')),
1456                                u'UnscaledNoDataValue': repr(self.GetLazyPropertyValue('UnscaledNoDataValue'))})
1457            else:
1458                self._LogDebug(_(u'%(class)s 0x%(id)08X: Retrieved lazy properties of %(dn)s: Dimensions=%(Dimensions)s, PhysicalDimensions=%(PhysicalDimensions)s, PhysicalDimensionsFlipped=%(PhysicalDimensionsFlipped)s, Shape=%(Shape)s, CoordIncrements=%(CoordIncrements)s, CornerCoords=%(CornerCoords)s, UnscaledDataType=%(UnscaledDataType)s.'),
1459                               {u'class': self.__class__.__name__,
1460                                u'id': id(self),
1461                                u'dn': self.DisplayName,
1462                                u'Dimensions': self.GetLazyPropertyValue('Dimensions'),
1463                                u'PhysicalDimensions': self.GetLazyPropertyValue('PhysicalDimensions'),
1464                                u'PhysicalDimensionsFlipped': repr(self.GetLazyPropertyValue('PhysicalDimensionsFlipped')),
1465                                u'Shape': repr(self.GetLazyPropertyValue('Shape')),
1466                                u'CoordIncrements': repr(self.GetLazyPropertyValue('CoordIncrements')),
1467                                u'CornerCoords': self.GetLazyPropertyValue('CornerCoords'),
1468                                u'UnscaledDataType': repr(self.GetLazyPropertyValue('UnscaledDataType'))})
1469
1470        # If the caller is requesting the NoDataValue property and we
1471        # failed to retrieve it from the Describe object, we must
1472        # retrieve it with GDAL instead. If we already opened a
1473        # GDALDataset for this raster, just read the NoDataValue
1474        # directly from it. Otherwise create a temporary directory,
1475        # export a small portion of the raster to an IMG file, read
1476        # the NoData value with GDAL, and delete the temporary
1477        # directory.
1478
1479        if name == 'UnscaledNoDataValue' and self._NoDataValueRetrievalFailed:
1480            if self.ParentCollection._GDALDataset is None:
1481                import shutil, tempfile
1482                tempDir = tempfile.mkdtemp(prefix='GeoEco_Temp_')
1483                self._LogDebug(_(u'%(class)s 0x%(id)08X: Created temporary directory %(dir)s.'), {u'class': self.__class__.__name__, u'id': id(self), u'dir': tempDir})
1484           
1485                try:
1486                    tempRaster = os.path.join(tempDir, u'raster')
1487                    extent = u'%g %g %g %g' % (self.MinCoords['x',0], self.MinCoords['y',0], self.MaxCoords['x', min(2, self.Shape[1]-1)], self.MaxCoords['y', min(2, self.Shape[0]-1)])
1488
1489                    self._LogDebug(_(u'%(class)s 0x%(id)08X: Clipping %(dn)s to "%(extent)s", output "%(dest)s".'), {u'class': self.__class__.__name__, u'id': id(self), u'dn': self.DisplayName, u'extent': extent, u'dest': tempRaster})
1490
1491                    gp.Clip_management(path, extent, tempRaster)
1492
1493                    self._LogDebug(_(u'%(class)s 0x%(id)08X: Getting the NoDataValue of temporary raster "%(tempRaster)s" with GDAL.'), {u'class': self.__class__.__name__, u'id': id(self), u'tempRaster': tempRaster})
1494
1495                    gdal = self._gdal()
1496                    gdal.ErrorReset()
1497                    gdalconst = self._gdalconst()
1498
1499                    try:
1500                        gdalDataset = gdal.Open(self._Str(tempRaster), gdalconst.GA_ReadOnly)
1501                    except Exception, e:
1502                        gdal.ErrorReset()
1503                        raise RuntimeError(_(u'Temporary raster "%(tempRaster)s" could not be opened with the Geospatial Data Abstraction Library (GDAL). This was being done to work around a bug in ArcGIS that prevents us from obtaining the No Data value of %(dn)s. Because the workaround failed, the No Data value cannot be determined. Detailed error information: gdal.Open() reported %(e)s: %(msg)s.') % {u'tempRaster': tempRaster, u'dn': self.DisplayName, u'e': e.__class__.__name__, u'msg': self._Str(e)})
1504                    try:
1505                        try:
1506                            band = gdalDataset.GetRasterBand(1)
1507                        except Exception, e:
1508                            self._gdal().ErrorReset()
1509                            raise RuntimeError(_(u'Could not open band %(band)i of temporary raster "%(tempRaster)s" with the Geospatial Data Abstraction Library (GDAL). This was being done to work around a bug in ArcGIS that prevents us from obtaining the No Data value of %(dn)s. Because the workaround failed, the No Data value cannot be determined. Detailed error information: dataset.GetRasterBand(%(band)i) reported %(e)s: %(msg)s.') % {u'tempRaster': tempRaster, u'dn': self.DisplayName, u'band': self._Band, u'e': e.__class__.__name__, u'msg': self._Unicode(e)})
1510                        try:
1511                            noDataValue = band.GetNoDataValue()
1512                        finally:
1513                            del band
1514                    finally:
1515                        del gdalDataset
1516                finally:
1517                    self._LogDebug(_(u'%(class)s 0x%(id)08X: Deleting temporary directory %(dir)s.'), {u'class': self.__class__.__name__, u'id': id(self), u'dir': tempDir})
1518                    shutil.rmtree(tempDir, onerror=DatasetCollection._LogFailedRemoval)
1519
1520            else:
1521                band = self.ParentCollection._GDALDataset._OpenBand(self.Band)
1522                try:
1523                    noDataValue = band.GetNoDataValue()
1524                finally:
1525                    del band
1526               
1527            if noDataValue is not None and self.GetLazyPropertyValue('UnscaledDataType') in [u'int8', u'uint8', u'int16', u'uint16', u'int32', u'uint32']:
1528                noDataValue = int(noDataValue)
1529
1530            self._LogDebug(_(u'%(class)s 0x%(id)08X: Retrieved lazy properties of %(dn)s: UnscaledNoDataValue=%(UnscaledNoDataValue)s.'), {u'class': self.__class__.__name__, u'id': id(self), u'dn': self.DisplayName, u'UnscaledNoDataValue': repr(noDataValue)})
1531
1532            return noDataValue
1533
1534        # Return the value of the requested property.
1535
1536        return self.GetLazyPropertyValue(name)
1537
1538    def _ReadNumpyArray(self, sliceList):
1539
1540        # Instantiate a GDALDataset for the raster.
1541
1542        dataset = self.ParentCollection._InstantiateGDALDataset()
1543
1544        # The following code is copied from
1545        # GDALRasterBand._ReadNumpyArray(). Rather than instantiate a
1546        # GDALRasterBand simply to be able to call that function, I
1547        # just copied the code. This is a bit dodgy from an
1548        # encapsulation and maintenance point of view but the code is
1549        # so simple that I consider it a reasonable tradeoff.
1550        #
1551        # Open the dataset and retrieve the band object.
1552
1553        band = dataset._OpenBand(self.Band)
1554        try:
1555           
1556            # Get the data. Note that sliceList[0] contains the y
1557            # indices and sliceList[1] contains the x indices.
1558
1559            xoff = sliceList[1].start
1560            yoff = sliceList[0].start
1561            win_xsize = sliceList[1].stop - sliceList[1].start
1562            win_ysize = sliceList[0].stop - sliceList[0].start
1563
1564            self._LogDebug(_(u'%(class)s 0x%(id)08X: Band %(Band)i: Reading with GDAL a block of %(win_xsize)i columns by %(win_ysize)i rows at offsets x=%(xoff)i, y=%(yoff)i.') % {u'class': self.__class__.__name__, u'id': id(self), u'Band': self.Band, u'xoff': xoff, u'yoff': yoff, u'win_xsize': win_xsize, u'win_ysize': win_ysize})
1565
1566            try:
1567                data = band.ReadAsArray(xoff, yoff, win_xsize, win_ysize)
1568            except Exception, e:
1569                self._gdal().ErrorReset()
1570                raise RuntimeError(_(u'Failed to retrieve a block of data of %(win_xsize)i columns by %(win_ysize)i rows at offsets x=%(xoff)i, y=%(yoff)i from band %(band)i of %(dn)s (a local copy of %(dn2)s) with the Geospatial Data Abstraction Library (GDAL). Verify that the dataset exists, is accessible, and has the expected dimensions. Detailed error information: band.ReadAsArray(%(xoff)i, %(yoff)i, %(win_xsize)i, %(win_ysize)i) reported %(e)s: %(msg)s.') % {u'Band': self.Band, u'dn': dataset.DisplayName, u'dn2': self.ParentCollection.DisplayName, u'xoff': xoff, u'yoff': yoff, u'win_xsize': win_xsize, u'win_ysize': win_ysize, u'e': e.__class__.__name__, u'msg': self._Unicode(e)})
1571
1572            # Return the data along with the NoData value that GDAL is
1573            # using. Note that the data type and NoData value may not
1574            # be what our UnscaledDataType and UnscaledNoDataValue
1575            # properties call for. If they are different, our caller
1576            # (Grid._ReadData) is responsible for handling it.
1577
1578            return data, band.GetNoDataValue()
1579
1580        finally:
1581            del band
1582
1583        # Return the array.
1584
1585        return data
1586
1587    @classmethod
1588    def ConstructFromArcGISPath(cls, path, decompressedFileToReturn=None, queryableAttributeValues=None, lazyPropertyValues=None, cacheDirectory=None):
1589        # TODO: cls.__doc__.Obj.ValidateMethodInvocation()
1590
1591        # Get the geoprocessor Describe object's DataType for the
1592        # path. If it is a RasterBand, instantiate an ArcGISRaster for
1593        # its parent and then query it for the ArcGISRasterBand that
1594        # has the specified band number.
1595
1596        d = GeoprocessorManager.GetWrappedGeoprocessor().Describe(path)
1597        dataType = d.DataType.lower()
1598        if dataType == 'rasterband':
1599            arcGISRaster = ArcGISRaster(d.Path, decompressedFileToReturn=decompressedFileToReturn, queryableAttributeValues=queryableAttributeValues, lazyPropertyValues=lazyPropertyValues, cacheDirectory=cacheDirectory)
1600            return arcGISRaster.QueryDatasets(u'Band = ' + str(int(d.Name.split('_')[1])), reportProgress=False)[0]
1601
1602        # If it is a RasterDataset, RasterLayer, or File (which could
1603        # be a compressed raster), instantiate an ArcGISRaster for it
1604        # and query it for the first ArcGISRasterBand. If it has
1605        # multiple bands, report a warning.
1606
1607        if dataType in ['rasterdataset', 'rasterlayer', 'file']:
1608            arcGISRaster = ArcGISRaster(path, decompressedFileToReturn=decompressedFileToReturn, queryableAttributeValues=queryableAttributeValues, lazyPropertyValues=lazyPropertyValues, cacheDirectory=cacheDirectory)
1609            if d.BandCount > 1:
1610                cls._LogWarning(_(u'%(dn)s has %(bands)i bands. The first band will be used.') % {u'dn': arcGISRaster.DisplayName, u'bands': d.BandCount})
1611            return arcGISRaster.QueryDatasets(u'Band = 1', reportProgress=False)[0]
1612
1613        # If we got to here, we do not know how to open it.
1614
1615        raise ValueError(_(u'Cannot open "%(path)s" as an ArcGIS raster band, raster dataset, or raster layer. ArcGIS reports that it is a "%(dataType)".') % {u'path': path, u'dataType': d.DataType})
1616
1617
1618class ArcGISCopyableTable(object):
1619    __doc__ = DynamicDocString()
1620
1621    def GetArcGISCopyablePath(self):
1622        raise NotImplementedError(_(u'The GetArcGISCopyablePath method of class %s has not been implemented.') % self.__class__.__name__)
1623
1624
1625class ArcGISTable(Table, ArcGISCopyableTable):
1626    __doc__ = DynamicDocString()
1627 
1628    def _GetPath(self):
1629        return self._Path
1630
1631    Path = property(_GetPath, doc=DynamicDocString())
1632
1633    def _GetArcGISDataType(self):
1634        return self.GetLazyPropertyValue('ArcGISDataType')
1635
1636    ArcGISDataType = property(_GetArcGISDataType, doc=DynamicDocString())
1637
1638    def _GetArcGISPhysicalDataType(self):
1639        return self.GetLazyPropertyValue('ArcGISPhysicalDataType')
1640
1641    ArcGISPhysicalDataType = property(_GetArcGISPhysicalDataType, doc=DynamicDocString())
1642
1643    def _GetAutoDeleteFieldAddedByArcGIS(self):
1644        return self._AutoDeleteFieldAddedByArcGIS
1645
1646    AutoDeleteFieldAddedByArcGIS = property(_GetAutoDeleteFieldAddedByArcGIS, doc=DynamicDocString())
1647
1648    def __init__(self, path, autoDeleteFieldAddedByArcGIS=True, parentCollection=None, queryableAttributeValues=None, lazyPropertyValues=None, cacheDirectory=None):       # TODO: can't remove cacheDirectory without changing ArcGISWorkspace._ConstructFoundObject
1649        self.__doc__.Obj.ValidateMethodInvocation()
1650
1651        # Initialize our properties.
1652
1653        self._Path = path
1654       
1655        if parentCollection is None:
1656            self._DisplayName = _(u'ArcGIS table "%(path)s"') % {u'path': path}
1657        else:
1658            self._DisplayName = _(u'ArcGIS table "%(path)s"') % {u'path': os.path.join(parentCollection.Path, path)}
1659
1660        self._AutoDeleteFieldAddedByArcGIS = autoDeleteFieldAddedByArcGIS
1661        self._DeletedFieldAddedByArcGIS = False
1662
1663        # Initialize the base class.
1664       
1665        super(ArcGISTable, self).__init__(parentCollection=parentCollection, queryableAttributeValues=queryableAttributeValues, lazyPropertyValues=lazyPropertyValues)
1666
1667    def _GetDisplayName(self):
1668        return self._DisplayName
1669
1670    def _GetFullPath(self):
1671        if self.ParentCollection is None:
1672            return self._Path
1673        return os.path.join(self.ParentCollection.Path, self._Path)
1674
1675    def _GetLazyPropertyPhysicalValue(self, name):
1676
1677        # If it is not a known lazy property, return None.
1678
1679        if name not in ['SpatialReference', 'HasOID', 'OIDFieldName', 'GeometryType', 'GeometryFieldName', 'MaxStringLength', 'Fields', 'ArcGISDataType', 'ArcGISPhysicalDataType']:
1680            return None
1681
1682        # Get the geoprocessor Describe object for this dataset and
1683        # verify that it is a table.
1684
1685        gp = GeoprocessorManager.GetWrappedGeoprocessor()
1686        path = self._GetFullPath()
1687       
1688        if not gp.Exists(path):
1689            raise ValueError(_(u'Failed to open ArcGIS table "%(path)s". ArcGIS reports that it does not exist.') % {u'path': path})
1690
1691        d = gp.Describe(path)
1692        if not hasattr(d, 'Fields') or d.Fields is None or d.Fields.Next() is None:
1693            raise ValueError(_(u'Failed to open ArcGIS dataset "%(path)s" as a table. ArcGIS reports that this object does not have any fields, therefore it cannot be accessed as a table.') % {u'path': path})
1694
1695        # Get the ArcGIS data type of this table and the data type of
1696        # its catalog path and set the display name to something more
1697        # descriptive.
1698
1699        arcGISDataType = self._Unicode(d.DataType)
1700        arcGISPhysicalDataType = self._Unicode(gp.Describe(d.CatalogPath).DataType)
1701       
1702        self.SetLazyPropertyValue('ArcGISDataType', arcGISDataType)
1703        self.SetLazyPropertyValue('ArcGISPhysicalDataType', arcGISPhysicalDataType)
1704
1705        if arcGISDataType == arcGISPhysicalDataType:
1706            self._DisplayName = _(u'ArcGIS %(dt)s "%(path)s"') % {u'dt': arcGISDataType, u'path': path}
1707        else:
1708            self._DisplayName = _(u'ArcGIS %(dt)s "%(path)s" of %(pdt)s "%(cp)s"') % {u'dt': arcGISDataType, u'path': path, u'pdt': arcGISPhysicalDataType, u'cp': d.CatalogPath}
1709
1710        # Get the rest of the lazy properties.
1711
1712        if hasattr(d, 'OIDFieldName') and isinstance(d.OIDFieldName, basestring) and len(d.OIDFieldName) > 0:
1713            self.SetLazyPropertyValue('HasOID', True)
1714            self.SetLazyPropertyValue('OIDFieldName', self._Unicode(d.OIDFieldName))
1715        else:
1716            self.SetLazyPropertyValue('HasOID', False)
1717            self.SetLazyPropertyValue('OIDFieldName', None)
1718
1719        geometryType = None
1720        geometryFieldName = None
1721       
1722        if hasattr(d, 'ShapeFieldName') and isinstance(d.ShapeFieldName, basestring) and len(d.ShapeFieldName) > 0:
1723            geometryFieldName = self._Unicode(d.ShapeFieldName)
1724            shapeType = d.ShapeType.lower()
1725            if shapeType == 'point':
1726                if hasattr(d, 'HasZ') and bool(d.HasZ):
1727                    geometryType = u'Point25D'
1728                else:
1729                    geometryType = u'Point'
1730            elif shapeType == 'multipoint':
1731                if hasattr(d, 'HasZ') and bool(d.HasZ):
1732                    geometryType = u'MultiPoint25D'
1733                else:
1734                    geometryType = u'MultiPoint'
1735            elif shapeType == 'polyline':
1736                if hasattr(d, 'HasZ') and bool(d.HasZ):
1737                    geometryType = u'MultiLineString25D'
1738                else:
1739                    geometryType = u'MultiLineString'
1740            elif shapeType == 'polygon':
1741                if hasattr(d, 'HasZ') and bool(d.HasZ):
1742                    geometryType = u'MultiPolygon25D'
1743                else:
1744                    geometryType = u'MultiPolygon'
1745            else:
1746                self._LogWarning(_(u'The %(dn)s has an unsupported shape type "%(st)s". Geometry will not be available for this dataset.'), {u'dn': self._DisplayName, u'st': d.ShapeType})
1747                geometryFieldName = None
1748
1749        self.SetLazyPropertyValue('GeometryType', geometryType)
1750        self.SetLazyPropertyValue('GeometryFieldName', geometryFieldName)
1751       
1752        if geometryType is not None:
1753            self.SetLazyPropertyValue('SpatialReference', Dataset.ConvertSpatialReference('arcgis', gp.CreateSpatialReference_management(d.SpatialReference).split(';')[0], 'obj'))
1754        else:
1755            self.SetLazyPropertyValue('SpatialReference', None)
1756
1757        if arcGISDataType.lower() in [u'dbasetable', u'textfile', u'shapefile']:
1758            self.SetLazyPropertyValue('MaxStringLength', 254)
1759        elif arcGISDataType.lower() == u'arcinfotable':
1760            self.SetLazyPropertyValue('MaxStringLength', 320)
1761        else:
1762            self.SetLazyPropertyValue('MaxStringLength', None)
1763
1764        # Get the fields.
1765
1766        fields = []
1767        fieldsObj = d.Fields
1768        f = fieldsObj.Next()
1769        while f is not None:
1770            fields.append(self._ConstructFieldObject(f))
1771            f = fieldsObj.Next()
1772
1773        self.SetLazyPropertyValue('Fields', tuple(fields))
1774
1775        # Log a debug message.
1776       
1777        if self._DebugLoggingEnabled():
1778            self._LogDebug(_(u'%(class)s 0x%(id)08X: Retrieved lazy properties of %(dn)s: ArcGISDataType=%(ArcGISDataType)s, ArcGISPhysicalDataType=%(ArcGISPhysicalDataType)s, MaxStringLength=%(MaxStringLength)s, HasOID=%(HasOID)s, OIDFieldName=%(OIDFieldName)s, GeometryType=%(GeometryType)s, GeometryFieldName=%(GeometryFieldName)s, SpatialReference=%(SpatialReference)s.'),
1779                           {u'class': self.__class__.__name__,
1780                            u'id': id(self),
1781                            u'dn': self.DisplayName,
1782                            u'ArcGISDataType': self.GetLazyPropertyValue('ArcGISDataType'),
1783                            u'ArcGISPhysicalDataType': self.GetLazyPropertyValue('ArcGISPhysicalDataType'),
1784                            u'MaxStringLength': repr(self.GetLazyPropertyValue('MaxStringLength')),
1785                            u'HasOID': repr(self.GetLazyPropertyValue('HasOID')),
1786                            u'OIDFieldName': repr(self.GetLazyPropertyValue('OIDFieldName')),
1787                            u'GeometryType': repr(self.GetLazyPropertyValue('GeometryType')),
1788                            u'GeometryFieldName': repr(self.GetLazyPropertyValue('GeometryFieldName')),
1789                            u'SpatialReference': repr(Dataset.ConvertSpatialReference('obj', self.GetLazyPropertyValue('SpatialReference'), 'arcgis'))})
1790
1791            for i, f in enumerate(fields):
1792                self._LogDebug(_(u'%(class)s 0x%(id)08X: Field %(i)i: Name=%(Name)s, DataType=%(DataType)s, Length=%(Length)s, Precision=%(Precision)s, IsNullable=%(IsNullable)s, IsSettable=%(IsSettable)s.'),
1793                               {u'class': self.__class__.__name__,
1794                                u'id': id(self),
1795                                u'i': i,
1796                                u'Name': f.Name,
1797                                u'DataType': f.DataType,
1798                                u'Length': repr(f.Length),
1799                                u'Precision': repr(f.Precision),
1800                                u'IsNullable': repr(f.IsNullable),
1801                                u'IsSettable': repr(f.IsSettable)})
1802
1803        # Return the value of the requested property.
1804
1805        return self.GetLazyPropertyValue(name)
1806
1807    def _ConstructFieldObject(self, f):
1808        dataType = f.Type.lower()
1809        if dataType == 'oid':
1810            dataType = 'oid'
1811        elif dataType == 'geometry':
1812            dataType = 'geometry'
1813        elif dataType == 'smallinteger':
1814            dataType = 'int16'
1815        elif dataType == 'integer':
1816            dataType = 'int32'
1817        elif dataType == 'single':
1818            dataType = 'float32'
1819        elif dataType == 'double':
1820            dataType = 'float64'
1821        elif dataType == 'string':
1822            dataType = 'string'
1823        elif dataType == 'date':
1824            if self.ArcGISDataType.lower() in ['dbasetable', 'shapefile', 'arcinfotable']:
1825                dataType = 'date'
1826            else:
1827                dataType = 'datetime'
1828        elif dataType == 'blob':
1829            dataType = 'binary'
1830        else:
1831            dataType = 'unknown'
1832        isNullable = f.IsNullable
1833        isNullable = isinstance(isNullable, (types.BooleanType, types.IntType)) and bool(isNullable) or isinstance(isNullable, basestring) and isNullable.lower() == 'true'
1834        isSettable = dataType != 'oid' and not (f.Name.lower() == 'shape_length' and self.GetLazyPropertyValue('GeometryType') in [u'MultiLineString', u'MultiLineString25D', u'MultiPolygon', u'MultiPolygon25D']) and not (f.Name.lower() == 'shape_area' and self.GetLazyPropertyValue('GeometryType') in [u'MultiPolygon', u'MultiPolygon25D'])
1835        return Field(f.Name, dataType, f.Length, f.Precision, isNullable, isSettable)
1836
1837    @classmethod
1838    def _TestCapability(cls, capability):
1839        if capability in ['setspatialreference', 'addfield', 'deletefield', 'selectcursor', 'updatecursor', 'insertcursor', 'updaterow', 'deleterow']:      # TODO: Test all of this for INFO tables.
1840            return None
1841
1842        if capability in ['int16 datatype', 'int32 datatype', 'float32 datatype', 'float64 datatype', 'string datatype']:
1843            return None
1844
1845        if capability == 'date datatype':
1846            if isinstance(cls, ArcGISTable) and cls.ArcGISPhysicalDataType.lower() not in ['shapefile', 'dbftable', 'arcinfotable']:
1847                return TypeError(_('Cannot create a field of data type "date" in %(dn)s because that data type is not supported by the ArcGIS %(dt)s data format. The ArcGIS %(dt)s data format can store dates with times, but not dates without times. Try the data type "datetime" instead.') % {u'dn': cls.DisplayName, u'dt': cls.ArcGISPhysicalDataType})
1848            return None
1849
1850        if capability == 'datetime datatype':
1851            if isinstance(cls, ArcGISTable) and cls.ArcGISPhysicalDataType.lower() in ['shapefile', 'dbftable', 'arcinfotable']:
1852                return TypeError(_('Cannot create a field of data type "datetime" in %(dn)s because that data type is not supported by the ArcGIS %(dt)s data format. The ArcGIS %(dt)s can store dates, but not dates with times. Try the data type "date" instead.') % {u'dn': cls.DisplayName, u'dt': cls.ArcGISPhysicalDataType})
1853            return None
1854
1855        if capability == 'binary datatype':
1856            if isinstance(cls, ArcGISTable) and cls.ArcGISPhysicalDataType.lower() in ['shapefile', 'dbftable', 'arcinfotable']:
1857                return TypeError(_('Cannot create a field of data type "binary" in %(dn)s because that data type is not supported by the ArcGIS %(dt)s data format.') % {u'dn': cls.DisplayName, u'dt': cls.ArcGISPhysicalDataType})
1858            return None
1859
1860        if capability.endswith(' datatype'):
1861            if isinstance(cls, ArcGISTable):
1862                return TypeError(_('Cannot create a field of data type "%(dt)s" in %(dn)s because that data type is not supported by the ArcGIS %(dt2)s data format.') % {u'dt': capability.split()[0], u'dn': cls.DisplayName, u'dt2': cls.ArcGISPhysicalDataType})
1863            else:
1864                return TypeError(_('Cannot create a field of data type "%(dt)s" because that data type is not supported by ArcGIS.') % {u'dt': capability.split()[0]})
1865
1866        if capability.endswith(' isnullable'):
1867            if isinstance(cls, ArcGISTable) and cls.ArcGISPhysicalDataType.lower() in ['shapefile', 'dbftable'] and not capability.startswith('date '):
1868                return TypeError(_('Cannot create a nullable field in %(dn)s because the ArcGIS %(dt)s data format does not support null values, except in date fields.') % {u'dn': cls.DisplayName, u'dt': cls.ArcGISPhysicalDataType})
1869            return None
1870
1871        if isinstance(cls, ArcGISTable):
1872            return RuntimeError(_(u'The %(cls)s class does not support the "%(cap)s" capability.') % {u'cls': cls.__class__.__name__, u'cap': capability})
1873        return RuntimeError(_(u'The %(cls)s class does not support the "%(cap)s" capability.') % {u'cls': cls.__name__, u'cap': capability})
1874
1875    @classmethod
1876    def _GetSRTypeForSetting(cls):
1877        return 'ArcGIS'
1878
1879    def _SetSpatialReference(self, sr):
1880        GeoprocessorManager.GetWrappedGeoprocessor().DefineProjection_management(self._GetFullPath(), sr)
1881
1882    def GetArcGISCopyablePath(self):
1883        return self._GetFullPath()
1884
1885    def _AddField(self, name, dataType, length, precision, isNullable):
1886        if dataType == 'int16':
1887            dataType = 'SHORT'
1888        elif dataType == 'int32':
1889            dataType = 'LONG'
1890        elif dataType == 'float32':
1891            dataType = 'FLOAT'
1892        elif dataType == 'float64':
1893            dataType = 'DOUBLE'
1894        elif dataType == 'string':
1895            dataType = 'TEXT'
1896        elif dataType == 'date' or dataType == 'datetime':
1897            dataType = 'DATE'
1898        elif dataType == 'binary':
1899            dataType = 'BLOB'
1900        else:
1901            raise NotImplementedError(_(u'The _AddField method of class %(cls)s does not support the %(dt)s data type.') % {u'cls': self.__class__.__name__, u'dt': dataType})
1902
1903        gp = GeoprocessorManager.GetWrappedGeoprocessor()
1904        newName = gp.ValidateFieldName(name, self._GetFullPath())
1905        if newName != name:
1906            raise ValueError(_(u'The name "%(name)s" is prohibited by either ArcGIS or the underlying database or file format. ArcGIS suggested the name "%(newName)s" instead.') % {u'name': name, u'newName': newName})
1907
1908        gp.AddField_management(self._GetFullPath(), name, dataType)
1909
1910        if self.AutoDeleteFieldAddedByArcGIS and not self._DeletedFieldAddedByArcGIS:
1911            if self.ArcGISPhysicalDataType.lower() == 'shapefile' and self.GetFieldByName('Id') is not None:
1912                self.DeleteField('Id')
1913            elif self.ArcGISPhysicalDataType.lower() == 'dbasetable' and self.GetFieldByName('Field1') is not None:
1914                self.DeleteField('Field1')
1915            self._DeletedFieldAddedByArcGIS = True
1916
1917        return self._ConstructFieldObject(gp.ListFields(self._GetFullPath(), name).Next())
1918
1919    def _DeleteField(self, name):
1920        GeoprocessorManager.GetWrappedGeoprocessor().DeleteField_management(self._GetFullPath(), name)
1921
1922    def _GetRowCount(self):
1923        return GeoprocessorManager.GetWrappedGeoprocessor().GetCount_management(self._GetFullPath())
1924
1925    def _OpenSelectCursor(self, fields, where, orderBy, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural):
1926        return ArcGISSelectCursor(self, fields, where, orderBy, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural)
1927
1928    def _OpenUpdateCursor(self, fields, where, orderBy, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural):
1929        return ArcGISUpdateCursor(self, fields, where, orderBy, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural)
1930
1931    def _OpenInsertCursor(self, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural):
1932        return ArcGISInsertCursor(self, rowCount, reportProgress, rowDescriptionSingular, rowDescriptionPlural)
1933
1934    @classmethod
1935    def _RemoveExistingDatasetsFromList(cls, path, datasets, progressReporter):
1936        numDatasets = len(datasets)
1937        if GeoprocessorManager.GetWrappedGeoprocessor().Exists(path):
1938            cls._LogDebug(_(u'%(class)s: ArcGIS table or feature class "%(path)s" exists.'), {u'class': cls.__name__, u'path': path})
1939            while len(datasets) > 0:
1940                del datasets[0]
1941        else:
1942            cls._LogDebug(_(u'%(class)s: ArcGIS table or feature class "%(path)s" does not exist.'), {u'class': cls.__name__, u'path': path})
1943
1944        if progressReporter is not None:
1945            progressReporter.ReportProgress(numDatasets)
1946
1947    @classmethod
1948    def _ImportDatasetsToPath(cls, path, sourceDatasets, mode, progressReporter, options):
1949
1950        # Validate that there is only one source dataset. We do not
1951        # currently support importing multiple source datasets into a
1952        # single destination dataset. (This could theoretically be
1953        # supported in the future by appending multiple datasets into
1954        # the same destination.)
1955
1956        if len(sourceDatasets) > 1:
1957            raise ValueError(_(u'Cannot import %(count)i datasets into ArcGIS table or feature class "%(path)s". Importing of multiple datasets into a single table or feature class is not currently supported. If you are receiving this error from a tool that has a parameter that is a list of expressions that define the output dataset namen, you may have made a mistake in your expressions that caused the same output dataset name to be generated for multiple input datasets. If that is the problem, you can fix it by modifying the expressions to generate a unique output dataset name for each input dataset.') % {u'count': len(sourceDatasets), u'path': path})
1958
1959        # If the mode is 'replace' and the destination dataset exists,
1960        # delete it.
1961
1962        gp = GeoprocessorManager.GetWrappedGeoprocessor()
1963
1964        if mode == u'replace' and gp.Exists(path):
1965            cls._LogDebug(_(u'%(class)s: Deleting existing ArcGIS dataset "%(path)s".'), {u'class': cls.__name__, u'path': path})
1966            try:
1967                gp.Delete_management(path)
1968            except Exception, e:
1969                raise RuntimeError(_(u'Failed to delete the existing ArcGIS dataset "%(path)s" due to %(e)s: %(msg)s') % {u'path': path, u'e': e.__class__.__name__, u'msg': cls._Unicode(e)})
1970
1971        # Otherwise, make sure all the parent levels in the
1972        # destination path exist (directories, geodatabase, feature
1973        # dataset), creating them if necessary. Use ArcGIS tools such
1974        # as CreateFolder_management to create them, so the ArcGIS
1975        # catalog is aware of them.
1976
1977        elif not gp.Exists(os.path.dirname(path)):
1978
1979            # If the destination path is in the file system, figure
1980            # out whether it contains levels for a geodatabase and a
1981            # feature dataset. If neither, it must be a shapefile, DBF
1982            # table, or something similar.
1983
1984            if path[0] in ['/', '\\'] or hasattr(os.path, 'splitdrive') and os.path.splitdrive(path)[0] != '' or hasattr(os.path, 'splitunc') and os.path.splitunc(path)[0] != '':
1985                if os.path.splitext(os.path.dirname(path))[1].lower() in ['.mdb', '.gdb']:
1986                    featureDataset = None
1987                    geodatabase = os.path.dirname(path)
1988                    directory = os.path.dirname(geodatabase)
1989                elif os.path.splitext(os.path.dirname(os.path.dirname(path)))[1].lower() in ['.mdb', '.gdb']:
1990                    featureDataset = os.path.dirname(path)
1991                    geodatabase = os.path.dirname(featureDataset)
1992                    directory = os.path.dirname(geodatabase)
1993                else:
1994                    featureDataset = None
1995                    geodatabase = None
1996                    directory = os.path.dirname(path)
1997
1998                # Create the directory if it does not exist.
1999
2000                if not os.path.exists(directory):
2001                    cls._LogDebug(_(u'%(class)s: Creating directory "%(path)s".'), {u'class': cls.__name__, u'path': directory})
2002
2003                    if hasattr(os.path, 'splitdrive') and os.path.splitdrive(directory)[0] != '':
2004                        root, subdirs = os.path.splitdrive(directory)
2005                        root = root + '\\'
2006                    elif hasattr(os.path, 'splitunc') and os.path.splitunc(directory)[0] != '':
2007                        root, subdirs = os.path.splitunc(directory)
2008                    else:
2009                        root, subdirs = directory[0], directory[1:]
2010
2011                    subdirsList = []
2012                    while len(subdirs) > 1:
2013                        subdirsList.insert(0, os.path.basename(subdirs))
2014                        subdirs = os.path.dirname(subdirs)
2015
2016                    dirToCheck = root
2017                    for subdir in subdirsList:
2018                        if not os.path.isdir(os.path.join(dirToCheck, subdir)):
2019                            try:
2020                                gp.CreateFolder_management(dirToCheck, subdir)
2021                            except Exception, e:
2022                                raise RuntimeError(_(u'Failed to create the directory "%(path)s" due to %(e)s: %(msg)s') % {u'path': os.path.join(dirToCheck, subdir), u'e': e.__class__.__name__, u'msg': cls._Unicode(e)})
2023                        dirToCheck = os.path.join(dirToCheck, subdir)
2024
2025                # If the path includes a geodatabase and it does not
2026                # exist, create it.
2027
2028                if geodatabase is not None and not gp.Exists(geodatabase):
2029                    if os.path.splitext(geodatabase).lower() == '.mdb':
2030                        gp.CreatePersonalGDB_management(directory, os.path.basename(geodatabase))
2031                    else:
2032                        gp.CreateFileGDB_management(directory, os.path.basename(geodatabase))
2033
2034                # If the path includes a feature dataset and it does not
2035                # exist, create it.
2036
2037                if featureDataset is not None and not gp.Exists(featureDataset):
2038                    gp.CreateFeatureDataset_management(geodatabase, os.path.basename(featureDataset))
2039                   
2040            # TODO: Otherwise (the desination path is not in the file
2041            # system), make sure the destination exists and create the
2042            # feature dataset. I'm not willing to code this without
2043            # having an ArcSDE geodatabase to test it with.
2044
2045            else:
2046                raise NotImplementedError(_(u'Cannot import %(dn)s into destination ArcGIS table or feature class "%(path)s" because that destination is not a folder, personal geodatabase, or file geodatabase. Other destinations (e.g. ArcSDE geodatabases) are not fully supported. Please contact the author of this tool for assistance.') % {u'dn': sourceDatasets[0].DisplayName, u'path': path})
2047
2048        # At this point, the parent workspace of the destination
2049        # dataset exists, and the destination dataset does not exist.
2050        # If the source dataset is copyable with ArcGIS's
2051        # Copy_management tool, use it because it will be much faster
2052        # than manually copying each row.
2053
2054        if isinstance(sourceDatasets[0], ArcGISCopyableTable):
2055            arcGISCopyablePath = sourceDatasets[0].GetArcGISCopyablePath()
2056            try:
2057                cls._LogDebug(_(u'%(class)s: Copying "%(src)s" to ArcGIS dataset "%(path)s".'), {u'class': cls.__name__, u'src': arcGISCopyablePath, u'path': path})
2058                gp.Copy_management(arcGISCopyablePath, path)
2059            finally:
2060                sourceDatasets[0].Close()       # This will make sure it deletes temporary files, if it had to create any in order to make itself ArcGIS-copyable.
2061
2062        # Otherwise copy the rows manually. This will work with any
2063        # Table instance.
2064
2065        else:
2066            workspace = ArcGISWorkspace(os.path.dirname(path), ArcGISTable, pathParsingExpressions=[r'(?P<TableName>.+)'], queryableAttributes=(QueryableAttribute(u'TableName', _(u'Table name'), UnicodeStringTypeMetadata()),))
2067            workspace.ImportTable(os.path.basename(path), sourceDatasets[0], reportProgress=False)
2068
2069        # Report progress.
2070           
2071        if progressReporter is not None:
2072            progressReporter.ReportProgress()
2073
2074
2075class _ArcGISReadableCursor(object):
2076    __doc__ = DynamicDocString()
2077
2078    def _NextRow(self):
2079        self._Row = self._Cursor.Next()        # TODO: See if we can improve performance by not going through the _ArcGISWrapper when accessing rows.
2080        return self._Row is not None
2081
2082    def _GetValue(self, field):
2083        return self._Row.GetValue(field)
2084
2085    def _GetOID(self):
2086        return self._Row.GetValue(self._Table.OIDFieldName)
2087
2088    def _GetGeometry(self):
2089        ogr = self._Table._ogr()
2090        g = self._Row.GetValue(self._Table.GeometryFieldName)
2091
2092        if g is not None:
2093            if self._Table.GeometryType == 'Point':
2094                geometry = ogr.Geometry(ogr.wkbPoint)
2095                part = g.GetPart()
2096                geometry.AddPoint_2D(part.X, part.Y)
2097
2098            elif self._Table.GeometryType == 'Point25D':
2099                geometry = ogr.Geometry(ogr.wkbPoint25D)
2100                part = g.GetPart()
2101                geometry.AddPoint(part.X, part.Y, part.Z)
2102
2103            elif self._Table.GeometryType == 'MultiPoint':
2104                if g.PartCount == 1:
2105                    geometry = ogr.Geometry(ogr.wkbPoint)
2106                    part = g.GetPart(0)
2107                    geometry.AddPoint_2D(part.X, part.Y)
2108                else:
2109                    geometry = ogr.Geometry(ogr.wkbMultiPoint)
2110                    for i in range(g.PartCount):
2111                        part = g.GetPart(i)
2112                        point = ogr.Geometry(ogr.wkbPoint)
2113                        point.AddPoint_2D(part.X, part.Y)
2114                        geometry.AddGeometryDirectly(point)
2115
2116            elif self._Table.GeometryType == 'MultiPoint25D':
2117                if g.PartCount == 1:
2118                    geometry = ogr.Geometry(ogr.wkbPoint25D)
2119                    part = g.GetPart(0)
2120                    geometry.AddPoint(part.X, part.Y, part.Z)
2121                else:
2122                    geometry = ogr.Geometry(ogr.wkbMultiPoint25D)
2123                    for i in range(g.PartCount):
2124                        part = g.GetPart(i)
2125                        point = ogr.Geometry(ogr.wkbPoint)
2126                        point.AddPoint(part.X, part.Y, part.Z)
2127                        geometry.AddGeometryDirectly(point)
2128
2129            elif self._Table.GeometryType == 'MultiLineString':
2130                if g.PartCount == 1:
2131                    geometry = ogr.Geometry(ogr.wkbLineString)
2132                    part = g.GetPart(0)
2133                    point = part.Next()
2134                    while point is not None:
2135                        geometry.AddPoint_2D(point.X, point.Y)
2136                        point = part.Next()
2137                else:
2138                    geometry = ogr.Geometry(ogr.wkbMultiLineString)
2139                    for i in range(g.PartCount):
2140                        line = ogr.Geometry(ogr.wkbLineString)
2141                        part = g.GetPart(i)
2142                        point = part.Next()
2143                        while point is not None:
2144                            line.AddPoint_2D(point.X, point.Y)
2145                            point = part.Next()
2146                        geometry.AddGeometryDirectly(line)
2147
2148            elif self._Table.GeometryType == 'MultiLineString25D':
2149                if g.PartCount == 1:
2150                    geometry = ogr.Geometry(ogr.wkbLineString25D)
2151                    part = g.GetPart(0)
2152                    point = part.Next()
2153                    while point is not None:
2154                        geometry.AddPoint(point.X, point.Y, point.Z)
2155                        point = part.Next()
2156                else:
2157                    geometry = ogr.Geometry(ogr.wkbMultiLineString25D)
2158                    for i in range(g.PartCount):
2159                        line = ogr.Geometry(ogr.wkbLineString25D)
2160                        part = g.GetPart(i)
2161                        point = part.Next()
2162                        while point is not None:
2163                            line.AddPoint(point.X, point.Y, point.Z)
2164                            point = part.Next()
2165                        geometry.AddGeometryDirectly(line)
2166
2167            elif self._Table.GeometryType == 'MultiPolygon':
2168                if g.PartCount == 1:
2169                    geometry = ogr.Geometry(ogr.wkbPolygon)
2170                    ring = ogr.Geometry(ogr.wkbLinearRing)
2171                    part = g.GetPart(0)
2172                    point = part.Next()
2173                    while point is not None:
2174                        ring.AddPoint_2D(point.X, point.Y)
2175                        point = part.Next()
2176                        if point is None:
2177                            geometry.AddGeometryDirectly(ring)
2178                            point = part.Next()
2179                            if point is not None:
2180                                ring = ogr.Geometry(ogr.wkbLinearRing)
2181                else:
2182                    geometry = ogr.Geometry(ogr.wkbMultiPolygon)
2183                    for i in range(g.PartCount):
2184                        polygon = ogr.Geometry(ogr.wkbPolygon)
2185                        ring = ogr.Geometry(ogr.wkbLinearRing)
2186                        part = g.GetPart(i)
2187                        point = part.Next()
2188                        while point is not None:
2189                            ring.AddPoint_2D(point.X, point.Y)
2190                            point = part.Next()
2191                            if point is None:
2192                                polygon.AddGeometryDirectly(ring)
2193                                point = part.Next()
2194                                if point is not None:
2195                                    ring = ogr.Geometry(ogr.wkbLinearRing)
2196                        geometry.AddGeometryDirectly(polygon)
2197
2198            elif self._Table.GeometryType == 'MultiPolygon25D':
2199                if g.PartCount == 1:
2200                    geometry = ogr.Geometry(ogr.wkbPolygon25D)
2201                    ring = ogr.Geometry(ogr.wkbLinearRing)
2202                    part = g.GetPart(0)
2203                    point = part.Next()
2204                    while point is not None:
2205                        ring.AddPoint(point.X, point.Y, point.Z)
2206                        point = part.Next()
2207                        if point is None:
2208                            geometry.AddGeometryDirectly(ring)
2209                            point = part.Next()
2210                            if point is not None:
2211                                ring = ogr.Geometry(ogr.wkbLinearRing)
2212                else:
2213                    geometry = ogr.Geometry(ogr.wkbMultiPolygon25D)
2214                    for i in range(g.PartCount):
2215                        polygon = ogr.Geometry(ogr.wkbPolygon25D)
2216                        ring = ogr.Geometry(ogr.wkbLinearRing)
2217                        part = g.GetPart(i)
2218                        point = part.Next()
2219                        while point is not None:
2220                            ring.AddPoint(point.X, point.Y, point.Z)
2221                            point = part.Next()
2222                            if point is None:
2223                                polygon.AddGeometryDirectly(ring)
2224                                point = part.Next()
2225                                if point is not None:
2226                                    ring = ogr.Geometry(ogr.wkbLinearRing)
2227                        geometry.AddGeometryDirectly(polygon)
2228
2229            else:
2230                raise NotImplementedError(_(u'The %(dn)s has an unsupported geometry type "%(gt)s".') % {u'dn': self._Table.DisplayName, u'gt': self._Table.GeometryType})
2231       
2232        else:
2233            self._Table._LogWarning(_(u'The %(singular)s of %(dn)s with %(field)s = %(value)s has a null geometry.'), {u'singular': self._RowDescriptionSingular, u'dn': self._Table.DisplayName, u'field': self._Table.OIDFieldName, u'value': repr(self._Cursor.GetValue(self._Table.OIDFieldName))})
2234            geometry = None
2235
2236        return geometry
2237
2238
2239class _ArcGISWritableCursor(object):
2240    __doc__ = DynamicDocString()
2241
2242    def _SetValue(self, field, value):
2243        # TODO: Verify the following check
2244        if value is None and not GeoprocessorManager.GetGeoprocessorIsCOMObject() and (GeoprocessorManager.GetArcGISMajorVersion() < 9 or GeoprocessorManager.GetArcGISMajorVersion() == 9 and (GeoprocessorManager.GetArcGISMinorVersion() == 2 or GeoprocessorManager.GetArcGISMinorVersion() == 3 and GeoprocessorManager.GetArcGISServicePack() < 3)):
2245            if GeoprocessorManager.GetArcGISMajorVersion() == 9 and GeoprocessorManager.GetArcGISMinorVersion() == 3 and GeoprocessorManager.GetArcGISServicePack() == 2:       # This is ArcGIS 9.3.1
2246                raise RuntimeError(_(u'Cannot set the value of field %(field)s of this %(singular) of %(dn)s to NULL due to ArcGIS bug NIM010734. ESRI fixed this problem in ArcGIS 9.3.1 Service Pack 1. Please install Service Pack 1 and try again. For more information on the bug, search http://support.esri.com for the bug number (NIM010734).') % {u'singular': self._RowDescriptionSingular, u'dn': self._Table.DisplayName, u'field': field})
2247            raise RuntimeError(_(u'Cannot set the value of field %(field)s of this %(singular) of %(dn)s to NULL due to ArcGIS bug NIM010734. ESRI fixed this problem in ArcGIS 9.3.1 Service Pack 1. Please upgrade to ArcGIS 9.3.1, install Service Pack 1, and try again. If that is not possible, please contact the author of this tool for assistance. For more information on the bug, search http://support.esri.com for the bug number (NIM010734).') % {u'singular': self._RowDescriptionSingular, u'dn': self._Table.DisplayName, u'field': field})
2248       
2249        if isinstance(value, types.IntType) and self._Table.ArcGISPhysicalDataType in ['shapefile', 'dbftable']:      # TODO: Check limits of ArcInfoTable
2250            length = self._Table.GetFieldByName(field).Length
2251            if value >= 10**length or value <= -1 * 10**(length-1):
2252                raise ValueError(_(u'Cannot set the value of field %(field)s of this %(singular) of %(dn)s to %(value)s because that value is outside of the allowed range of values for the field (%(r1)i to %(r2)i).') % {u'singular': self._RowDescriptionSingular, u'dn': self._Table.DisplayName, u'field': field, u'value': repr(value), u'r1': -1 * 10**(length-1) + 1, u'r2': 10**length - 1})
2253
2254        self._Row.SetValue(field, value)
2255
2256    def _SetGeometry(self, geometry):
2257        if geometry.IsEmpty():
2258            raise ValueError(_(u'Cannot set the geometry of this %(singular)s of %(dn)s because the provided Geometry object is empty. ArcGIS does not support empty geometries.') % {u'singular': self._RowDescriptionSingular, u'dn': self._Table.DisplayName})
2259       
2260        ogr = self._Table._ogr()
2261        gp = GeoprocessorManager.GetWrappedGeoprocessor()
2262        geometryType = geometry.GetGeometryType()
2263       
2264        if geometryType in [ogr.wkbPoint, ogr.wkbPoint25D]:
2265            g = gp.CreateObject('Point')
2266            g.X = geometry.GetX()
2267            g.Y = geometry.GetY()
2268            if geometryType == ogr.wkbPoint25D:
2269                g.Z = geometry.GetZ()
2270       
2271        elif geometryType in [ogr.wkbLineString, ogr.wkbLineString25D]:
2272            g = gp.CreateObject('Array')
2273            p = gp.CreateObject('Point')
2274            for i in range(geometry.GetPointCount()):
2275                p.X = geometry.GetX(i)
2276                p.Y = geometry.GetY(i)
2277                if geometryType == ogr.wkbPoint25D:
2278                    p.Z = geometry.GetZ(i)
2279                g.Add(p)
2280       
2281        elif geometryType in [ogr.wkbPolygon, ogr.wkbPolygon25D]:
2282            g = gp.CreateObject('Array')
2283            a = gp.CreateObject('Array')
2284            p = gp.CreateObject('Point')
2285            for i in range(geometry.GetGeometryCount()):
2286                r = geometry.GetGeometryRef(i)
2287                firstX = r.GetX(0)
2288                firstY = r.GetY(0)
2289                if geometryType == ogr.wkbPolygon25D:
2290                    firstZ = r.GetZ(0)
2291                for j in range(r.GetPointCount()):
2292                    p.X = r.GetX(j)
2293                    p.Y = r.GetY(j)
2294                    if geometryType == ogr.wkbPolygon25D:
2295                        p.Z = r.GetZ(j)
2296                    a.Add(p)
2297                if r.GetX(j) != firstX or r.GetY(j) != firstY or geometryType == ogr.wkbPolygon25D and r.GetZ(j) != firstZ:
2298                    p.X = firstX
2299                    p.Y = firstY
2300                    if geometryType == ogr.wkbPolygon25D:
2301                        p.Z = firstZ
2302                    a.Add(p)
2303            g.Add(a)
2304       
2305        elif geometryType in [ogr.wkbMultiPoint, ogr.wkbMultiPoint25D]:
2306            g = gp.CreateObject('Array')
2307            p = gp.CreateObject('Point')
2308            for i in range(geometry.GetGeometryCount()):
2309                r = geometry.GetGeometryRef(i)
2310                p.X = r.GetX()
2311                p.Y = r.GetY()
2312                if geometryType == ogr.wkbMultiPoint25D:
2313                    p.Z = r.GetY()
2314                g.Add(p)
2315       
2316        elif geometryType in [ogr.wkbMultiLineString, ogr.wkbMultiLineString25D]:
2317            g = gp.CreateObject('Array')
2318            p = gp.CreateObject('Point')
2319            for i in range(geometry.GetGeometryCount()):
2320                r = geometry.GetGeometryRef(i)
2321                a = gp.CreateObject('Array')
2322                for j in range(r.GetPointCount()):
2323                    p.X = r.GetX(j)
2324                    p.Y = r.GetY(j)
2325                    if geometryType == ogr.wkbPoint25D:
2326                        p.Z = r.GetZ(j)
2327                    a.Add(p)
2328                g.Add(a)
2329
2330        elif geometryType in [ogr.wkbMultiPolygon, ogr.wkbMultiPolygon25D]:
2331            g = gp.CreateObject('Array')
2332            p = gp.CreateObject('Point')
2333            for i in range(geometry.GetGeometryCount()):
2334                r1 = geometry.GetGeometryRef(i)
2335                a = gp.CreateObject('Array')
2336                for j in range(r1.GetGeometryCount()):
2337                    r2 = r1.GetGeometryRef(j)
2338                    firstX = r2.GetX(0)
2339                    firstY = r2.GetY(0)
2340                    if geometryType == ogr.wkbPolygon25D:
2341                        firstZ = r2.GetZ(0)
2342                    for k in range(r2.GetPointCount()):
2343                        p.X = r2.GetX(k)
2344                        p.Y = r2.GetY(k)
2345                        if geometryType == ogr.wkbMultiPolygon25D:
2346                            p.Z = r2.GetZ(k)
2347                        a.Add(p)
2348                    if r2.GetX(k) != firstX or r2.GetY(k) != firstY or geometryType == ogr.wkbMultiPolygon25D and r2.GetZ(k) != firstZ:
2349                        p.X = firstX
2350                        p.Y = firstY
2351                        if geometryType == ogr.wkbMultiPolygon25D:
2352                            p.Z = firstZ
2353                        a.Add(p)
2354                g.Add(a)
2355
2356        else:
2357            raise NotImplementedError(_(u'The _SetGeometry method of class %(cls)s does not support for OGR geometry type %(gt)i.') % {u'cls': self.__class__.__name__, u'gt': geometryType})
2358
2359        self._Row.SetValue(self._Table.GeometryFieldName, g)
2360
2361
2362class ArcGISSelectCursor(_ArcGISReadableCursor, SelectCursor):
2363    __doc__ = DynamicDocString()
2364
2365    def _Open(self, fields, where, orderBy):
2366        if orderBy is not None and GeoprocessorManager.GetArcGISMajorVersion() == 9 and GeoprocessorManager.GetArcGISMinorVersion() < 2:
2367            raise RuntimeError(_(u'The installed version of ArcGIS does not allow you to specify the sort order of cursors. Please upgrade to ArcGSI 9.2 or later, or omit the orderBy parameter and try again.'))
2368
2369        if fields is not None:
2370            fields = u'; '.join(fields)
2371
2372        if orderBy is not None:
2373            orderBy = '; '.join(map(lambda s: s.strip().split()[0] + ' ' + s.strip().split()[1][0].upper(), orderBy.split(',')))
2374       
2375        self._Row = None
2376        self._Cursor = GeoprocessorManager.GetWrappedGeoprocessor().SearchCursor(self._Table._GetFullPath(), where, None, fields, orderBy)
2377
2378    def _Close(self):
2379        self._Row = None
2380        self._Cursor = None
2381        super(ArcGISSelectCursor, self)._Close()
2382
2383
2384class ArcGISUpdateCursor(_ArcGISReadableCursor, _ArcGISWritableCursor, UpdateCursor):
2385    __doc__ = DynamicDocString()
2386
2387    def _Open(self, fields, where, orderBy):
2388        if orderBy is not None and GeoprocessorManager.GetArcGISMajorVersion() == 9 and GeoprocessorManager.GetArcGISMinorVersion() < 2:
2389            raise RuntimeError(_(u'The installed version of ArcGIS does not allow you to specify the sort order of cursors. Please upgrade to ArcGSI 9.2 or later, or omit the orderBy parameter and try again.'))
2390
2391        if fields is not None:
2392            fields = u'; '.join(fields)
2393
2394        if orderBy is not None:
2395            orderBy = '; '.join(map(lambda s: s.strip().split()[0] + ' ' + s.strip().split()[1][0].upper(), orderBy.split(',')))
2396       
2397        self._Row = None
2398        self._Cursor = GeoprocessorManager.GetWrappedGeoprocessor().UpdateCursor(self._Table._GetFullPath(), where, None, fields, orderBy)
2399
2400    def _Close(self):
2401        self._Row = None
2402        self._Cursor = None
2403        super(ArcGISUpdateCursor, self)._Close()
2404
2405    def _UpdateRow(self):
2406        self._Cursor.UpdateRow(self._Row)
2407        self._Row = None
2408
2409    def _DeleteRow(self):
2410        self._Cursor.DeleteRow(self._Row)
2411        self._Row = None
2412
2413
2414class ArcGISInsertCursor(_ArcGISWritableCursor, InsertCursor):
2415    __doc__ = DynamicDocString()
2416
2417    def _Open(self):
2418        self._Row = None
2419        self._Cursor = GeoprocessorManager.GetWrappedGeoprocessor().InsertCursor(self._Table._GetFullPath())
2420
2421    def _Close(self):
2422        self._Row = None
2423        self._Cursor = None
2424        super(ArcGISInsertCursor, self)._Close()
2425
2426    def _SetValue(self, field, value):
2427        if self._Row is None:
2428            self._Row = self._Cursor.NewRow()
2429        super(ArcGISInsertCursor, self)._SetValue(field, value)
2430
2431    def _SetGeometry(self, geometry):
2432        if self._Row is None:
2433            self._Row = self._Cursor.NewRow()
2434        super(ArcGISInsertCursor, self)._SetGeometry(geometry)
2435
2436    def _InsertRow(self):
2437        self._Cursor.InsertRow(self._Row)
2438        self._Row = None
2439
2440
2441###############################################################################
2442# Metadata: module
2443###############################################################################
2444
2445from GeoEco.ArcGIS import ArcGISDependency
2446from GeoEco.Metadata import *
2447from GeoEco.Types import *
2448
2449AddModuleMetadata(shortDescription=_(u'Classes used to access tabular and raster datasets through the ArcGIS geoprocessor.'))
2450
2451###############################################################################
2452# Metadata: ArcGISTabularLayer class
2453###############################################################################
2454
2455AddClassMetadata(ArcGISTabularLayer,
2456    shortDescription=_(u'Represents a table, feature class, feature layer, or other table-like object accessible through the ArcGIS geoprocessor.'))
2457
2458# Public method: ArcGISTabularLayer.Open
2459
2460AddMethodMetadata(ArcGISTabularLayer.Open,
2461    shortDescription=_(u'Returns an ArcGISTabularLayer instance representing a table, feature class, or other table-like object accessible through the ArcGIS geoprocessor.'),
2462    isExposedToPythonCallers=True,
2463    dependencies=[ArcGISDependency(9, 1)])
2464
2465AddArgumentMetadata(ArcGISTabularLayer.Open, u'cls',
2466    typeMetadata=ClassOrClassInstanceTypeMetadata(cls=ArcGISTabularLayer),
2467    description=_(u'%s class or an instance of it.') % ArcGISTabularLayer.__name__)
2468
2469AddArgumentMetadata(ArcGISTabularLayer.Open, u'name',
2470    typeMetadata=UnicodeStringTypeMetadata(),
2471    description=_(
2472u"""Name of the table-like object to open.
2473
2474The object can be a table, table view, feature class, feature layer,
2475raster dataset, raster layer, or anything else that has fields and
2476that may be accessed through the ArcGIS geoprocessor."""))
2477
2478AddResultMetadata(ArcGISTabularLayer.Open, u'dataset',
2479    typeMetadata=ClassInstanceTypeMetadata(cls=ArcGISTabularLayer),
2480    description=_(
2481u"""An ArcGISTabularLayer instance representing the opened object.
2482
2483Due to the design of the ArcGIS geoprocessor, the returned instance
2484does not actually maintain an open connection or handle to the object.
2485The object will not be "locked" in any way. The instance does cache a
2486snapshot of the field list and other properties of the object and
2487assumes that these will not be changed for the lifetime of the
2488instance, except by calls placed to that instance."""))
2489
2490# Public method: ArcGISTabularLayer.Create
2491
2492AddMethodMetadata(ArcGISTabularLayer.Create,
2493    shortDescription=_(u'Creates a table or feature class with the ArcGIS geoprocessor and returns an ArcGISTabularLayer instance representing it.'),
2494    isExposedToPythonCallers=True,
2495    dependencies=[ArcGISDependency(9, 1)])
2496
2497CopyArgumentMetadata(ArcGISTabularLayer.Open, u'cls', ArcGISTabularLayer.Create, u'cls')
2498
2499AddArgumentMetadata(ArcGISTabularLayer.Create, u'workspace',
2500    typeMetadata=UnicodeStringTypeMetadata(),
2501    description=_(
2502u"""Existing ArcGIS workspace that the table or feature class should
2503be created within."""))
2504
2505AddArgumentMetadata(ArcGISTabularLayer.Create, u'name',
2506    typeMetadata=UnicodeStringTypeMetadata(),
2507    description=_(
2508u"""Name of the table or feature class to create.
2509
2510The ArcGIS Create Table geoprocessing tool will be used to create
2511tables. The Create Feature Class tool will be used to create
2512features."""))
2513
2514AddArgumentMetadata(ArcGISTabularLayer.Create, u'geometryType',
2515    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True, allowedValues=[u'Point', u'Point25D', u'MultiPoint', u'MultiPoint25D', u'LineString', u'MultiLineString', u'LineString25D', u'MultiLineString25D', u'Polygon', u'MultiPolygon', u'Polygon25D', u'MultiPolygon25D'], makeLowercase=True),
2516    description=_(
2517u"""Type of geometry to use to create the feature class, or None if a
2518table should be created.
2519
2520Note that ArcGIS does not support certain geometry types, such as
2521GeometryCollection, that may be available through other libraries such
2522as OGR."""))
2523
2524AddArgumentMetadata(ArcGISTabularLayer.Create, u'srType',
2525    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True, allowedValues=[u'WKT', u'ArcGIS', u'Proj4', u'Obj'], makeLowercase=True),
2526    description=_(
2527u"""Type of spatial reference that you are providing for the sr
2528parameter, or None if the spatial reference should remain undefined.
2529This parameter is ignored when creating a table (i.e. if geometryType
2530is None).
2531
2532The allowed values are:
2533
2534* WKT - sr is a WKT string in standard OGC format.
2535
2536* ArcGIS - sr is a WKT string in ESRI format, typically obtained from
2537  a dataset produced by ArcGIS. (The ESRI format differs from the OGC
2538  standard; various projections and parameters are named differently
2539  and certain nodes are not recognized. See the OSR documentation for
2540  more information.)
2541
2542* Proj4 - sr is a string suitable for passing to the Proj4 utility.
2543
2544* Obj - sr is an instance of the OSR SpatialReference class.
2545
2546You may provide a spatial reference of any of these types. If the type
2547is not 'ArcGIS', this function will use the OSR MorphToESRI function
2548to convert the spatial reference to ESRI format prior to creating the
2549feature class. Not all spatial references can be converted properly to
2550ESRI format. In some cases, OSR may report an error, but in others it
2551may create an ESRI string that is syntactically correct but is missing
2552expected WKT parameters. When using a spatial reference for the first
2553time, be sure to check the resulting feature class with ArcGIS to
2554ensure the spatial reference is correct."""))
2555
2556AddArgumentMetadata(ArcGISTabularLayer.Create, u'sr',
2557    typeMetadata=AnyObjectTypeMetadata(canBeNone=True),
2558    description=_(
2559u"""Spatial reference to use when creating the feature class, or None
2560if the spatial reference should remain undefined. This parameter is
2561ignored when creating a table.
2562
2563If you provide this parameter you must also provide the srType
2564parameter, which determines what type of spatial reference you are
2565providing."""))
2566
2567AddArgumentMetadata(ArcGISTabularLayer.Create, u'configKeyword',
2568    typeMetadata=AnyObjectTypeMetadata(canBeNone=True),
2569    description=_(
2570u"""ArcGIS configuration keyword that determines storage parameters
2571for the table or feature class. This parameter is usually needed only
2572in specific uncommon scenarios. For more information, please see the
2573ArcGIS documentation for the Create Table or Create Feature Class
2574tool."""))
2575
2576AddArgumentMetadata(ArcGISTabularLayer.Create, u'spatialGrid1',
2577    typeMetadata=FloatTypeMetadata(canBeNone=True, minValue=0.0),
2578    description=_(
2579u"""The size of the feature class's first spatial grid index. This
2580parameter is ignored when creating a table.
2581
2582The ArcGIS 9.3 documentation for the Create Feature Class tool
2583provides the following description for this parameter:
2584
2585"The following formats support spatial index grids: personal
2586geodatabase, file geodatabase or ArcSDE geodatabase. If this value is
2587left blank (or 0) a valid grid size will be calculated automatically."
2588"""))
2589
2590AddArgumentMetadata(ArcGISTabularLayer.Create, u'spatialGrid2',
2591    typeMetadata=FloatTypeMetadata(canBeNone=True, minValue=0.0),
2592    description=_(
2593u"""The size of the feature class's second spatial grid index. This
2594parameter is ignored when creating a table.
2595
2596The ArcGIS 9.3 documentation for the Create Feature Class tool
2597provides the following description for this parameter:
2598
2599"This value must be at least 3 times larger than the first index grid.
2600The following formats support more than one spatial index grids: file
2601geodatabase or ArcSDE geodatabase. For more information, see the Add
2602Spatial Index tool. Note that personal geodatabase support only one
2603spatial index grid." """))
2604
2605AddArgumentMetadata(ArcGISTabularLayer.Create, u'spatialGrid3',
2606    typeMetadata=FloatTypeMetadata(canBeNone=True, minValue=0.0),
2607    description=_(
2608u"""The size of the feature class's third spatial grid index. This
2609parameter is ignored when creating a table.
2610
2611The ArcGIS 9.3 documentation for the Create Feature Class tool
2612provides the following description for this parameter:
2613
2614"This value must be at least 3 times larger than the second index
2615grid. The following formats support more than one spatial index grids:
2616file geodatabase or ArcSDE geodatabase. Note that personal geodatabase
2617support only one spatial index grid." """))
2618
2619AddArgumentMetadata(ArcGISTabularLayer.Create, u'autoDeleteFieldAddedByArcGIS',
2620    typeMetadata=BooleanTypeMetadata(),
2621    description=_(
2622u"""If True (the default) and you are creating a shapefile, the "Id"
2623field added by ArcGIS will be automatically deleted when you add the
2624first field to the shapefile. This parameter is ignored when creating
2625a table or a feature class that is not a shapefile.
2626
2627ArcGIS requires that shapefiles have at least one field, in addition
2628to the OID and Shape fields. To meet this requirement, ArcGIS
2629automatically adds a field called "Id" to new shapefiles. The default
2630behavior of ArcGISTabularLayer is to automatically delete this field
2631the first time you call AddField on the returned ArcGISTabularLayer
2632instance, under the assumption that most people do not want the Id
2633field. You can override this behavior by passing False for this
2634parameter."""))
2635
2636AddResultMetadata(ArcGISTabularLayer.Create, u'dataset',
2637    typeMetadata=ClassInstanceTypeMetadata(cls=ArcGISTabularLayer),
2638    description=_(
2639u"""An ArcGISTabularLayer instance representing the created table or
2640feature class.
2641
2642Due to the design of the ArcGIS geoprocessor, the returned instance
2643does not actually maintain an open connection or handle to the object.
2644The object will not be "locked" in any way. The instance does cache a
2645snapshot of the field list and other properties of the object and
2646assumes that these will not be changed for the lifetime of the
2647instance, except by calls placed to that instance."""))
2648
2649# Public method: ArcGISTabularLayer.Delete
2650
2651AddMethodMetadata(ArcGISTabularLayer.Delete,
2652    shortDescription=_(u'Deletes a table, feature class, or other table-like object accessible through the ArcGIS geoprocessor.'),
2653    isExposedToPythonCallers=True,
2654    dependencies=[ArcGISDependency(9, 1)])
2655
2656CopyArgumentMetadata(ArcGISTabularLayer.Open, u'cls', ArcGISTabularLayer.Delete, u'cls')
2657
2658AddArgumentMetadata(ArcGISTabularLayer.Delete, u'name',
2659    typeMetadata=UnicodeStringTypeMetadata(),
2660    description=_(
2661u"""Name of the table-like object to delete with the ArcGIS
2662geoprocessor.
2663
2664The object can be a table, feature class, feature layer, raster
2665dataset, raster layer, or anything else that has fields and that may
2666be accessed through the ArcGIS geoprocessor. The ArcGIS Delete
2667geoprocessing tool will be used to delete it.
2668
2669In ArcGIS 9.2, 9.3, 9.3.1, and possibly later versions, that tool was
2670not capable of deleting tool views. If you use
2671ArcGISTabularLayer.Delete to delete a table view with ArcGIS
26729.2-9.3.1, it will detect the problem, report a warning saying the
2673table view was not deleted, and return successfully. Because we did
2674not know how later versions of ArcGIS would behave, we did not
2675implement this detection for later versions. Therefore, if the problem
2676exists in later versions, it is likely that an error will be reported
2677and the function will fail."""))
2678
2679AddArgumentMetadata(ArcGISTabularLayer.Delete, u'dataType',
2680    typeMetadata=UnicodeStringTypeMetadata(canBeNone=True),
2681    description=_(
2682u"""The type of object to delete, in the case where there are multiple
2683objects with the same name but different data types.
2684
2685This situation is rare, so this parameter should usually be omitted.
2686The ArcGIS documentation mentions only one case, where a geodatbase
2687contains a feature dataset and feature class with the same name. In
2688that case, you would specify 'FeatureClass' for this parameter to
2689delete the feature class."""))
2690
2691AddArgumentMetadata(ArcGISTabularLayer.Delete, u'failIfDoesNotExist',
2692    typeMetadata=BooleanTypeMetadata(),
2693    description=_(
2694u"""If True, this function will fail if the specified field does not
2695exist. If False (the default), this function will succeed but report a
2696warning if the field does not exist."""))
2697
2698# Public properties
2699
2700AddPropertyMetadata(ArcGISTabularLayer.Name,
2701    typeMetadata=UnicodeStringTypeMetadata(),
2702    shortDescription=_(u'ArcGIS\'s name of this object, suitable for passing to ArcGIS geoprocessing tools.'))
2703
2704AddPropertyMetadata(ArcGISTabularLayer.DataType,
2705    typeMetadata=UnicodeStringTypeMetadata(),
2706    shortDescription=_(u'The ArcGIS data type of this object, obtained from the DataType property of the geoprocessor Describe object.'))
2707
2708AddPropertyMetadata(ArcGISTabularLayer.PhysicalDataType,
2709    typeMetadata=UnicodeStringTypeMetadata(),
2710    shortDescription=_(u'If this object is an ArcGIS feature layer, this is the ArcGIS data type of the feature class that backs the layer, obtained from the DataType property of the geoprocessor Describe object.'))
2711
2712AddPropertyMetadata(ArcGISTabularLayer.AutoDeleteFieldAddedByArcGIS,
2713    typeMetadata=BooleanTypeMetadata(),
2714    shortDescription=_(u'If this object was returned by ArcGISTabularLayer.Create, this is the value of the autoDeleteFieldAddedByArcGIS parameter passed to Create.'))
2715
2716###############################################################################
2717# Metadata: ArcGISSelectCursor class
2718###############################################################################
2719
2720AddClassMetadata(ArcGISSelectCursor,
2721    shortDescription=_(u'Represents a forward-only cursor used to read rows from a table, feature class, or other table-like object accessible through the ArcGIS geoprocessor.'),
2722    longDescription=_(
2723u"""To use the orderBy parameter when opening a cursor, ArcGIS 9.2 or
2724later is required."""))
2725
2726###############################################################################
2727# Metadata: ArcGISUpdateCursor class
2728###############################################################################
2729
2730AddClassMetadata(ArcGISUpdateCursor,
2731    shortDescription=_(u'Represents a forward-only cursor used to update or delete rows in a table, feature class, or other table-like object accessible through the ArcGIS geoprocessor.'),
2732    longDescription=_(
2733u"""Update cursors are fully supported, including updating and
2734deleting rows, for most table-like objects that ArcGIS can access.
2735
2736To use the orderBy parameter when opening a cursor, ArcGIS 9.2 or
2737later is required."""))
2738
2739###############################################################################
2740# Metadata: ArcGISInsertCursor class
2741###############################################################################
2742
2743AddClassMetadata(ArcGISInsertCursor,
2744    shortDescription=_(u'Represents a forward-only cursor used to insert rows into a table, feature class, or other table-like object accessible through the ArcGIS geoprocessor.'),
2745    longDescription=_(
2746u"""Insert cursors are supported for most table-like objects that
2747ArcGIS can access."""))
2748
2749###############################################################################
2750# Metadata: ArcGISWorkspace class
2751###############################################################################
2752
2753AddClassMetadata(ArcGISWorkspace,
2754    shortDescription=_(u'TODO: Add description'))
2755
2756# TODO: Add metadata
2757
2758###############################################################################
2759# Metadata: ArcGISRaster class
2760###############################################################################
2761
2762_UseUnscaledDataDescription = _(
2763u"""If True and the original data is stored as integers that are
2764processed through a "scaling equation" to produce the actual floating
2765point values, the output rasters will be created with the integers
2766rather the floating point values. If False, or the original data is
2767not processed through a scaling equation, the output rasters will be
2768created using the data's original data type.""")
2769
2770_CalculateStatisticsDescription = _(
2771u"""If True, statistics will be calculated for the output rasters
2772using the ArcGIS Calculate Statistics geoprocessing tool. This is
2773usually a good idea for most raster formats because ArcGIS will only
2774display them with helpful colors and gradients if statistics have been
2775calculated. For certain formats, the explicit calculation of
2776statistics is not necessary because it happens automatically when the
2777rasters are created. If you're using one of those formats, you can set
2778this option to False to speed up the creation of the output
2779rasters.""")
2780
2781_BuildRATDescription = _(
2782u"""If True and the output rasters use an integer data type, raster
2783attribute tables (RATs) will be built for the output rasters using the
2784ArcGIS Build Raster Attribute Table tool. Raster attribute tables are
2785essentially histograms: they store the counts of cells having each
2786value. If you do not need this information, you can skip the building
2787of raster attribute tables to speed up the creation of the output
2788rasters. Note that for certain raster formats, such as ArcInfo Binary
2789Grid, the explicit buliding of raster attribute tables is not
2790necessary because it happens automatically when the rasters are
2791created.
2792
2793This option is ignored if the output rasters use a floating point data
2794type.""")
2795
2796_BuildPyramidsDescription = _(
2797u"""If True, pyramids will be built for the output rasters using the
2798ArcGIS Build Pyramids tool. Pyramids, also known as overviews, are
2799reduced resolution versions of the rasters that can improve the speed
2800at which they are displayed in the ArcGIS user interface.""")
2801
2802AddClassMetadata(ArcGISRaster,
2803    shortDescription=_(u'TODO: Add description'))
2804
2805# TODO: Add metadata
2806
2807###############################################################################
2808# Metadata: ArcGISRasterBand class
2809###############################################################################
2810
2811AddClassMetadata(ArcGISRasterBand,
2812    shortDescription=_(u'TODO: Add description'))
2813
2814# TODO: Add metadata
2815
2816###############################################################################
2817# Metadata: ArcGISTable class
2818###############################################################################
2819
2820AddClassMetadata(ArcGISTable,
2821    shortDescription=_(u'TODO: Add description'))
2822
2823# Public method: ArcGISTable.__init__
2824
2825AddMethodMetadata(ArcGISTable.__init__,
2826    shortDescription=_(u'Constructs an instance representing table accessible through the ArcGIS geoprocessor.'),
2827    isExposedToPythonCallers=True,
2828    dependencies=[ArcGISDependency(9, 1)])
2829
2830AddArgumentMetadata(ArcGISTable.__init__, u'self',
2831    typeMetadata=ClassInstanceTypeMetadata(cls=ArcGISTable),
2832    description=_(u'ArcGISTable instance.'))
2833
2834AddArgumentMetadata(ArcGISTable.__init__, u'path',
2835    typeMetadata=UnicodeStringTypeMetadata(),
2836    description=_(
2837u"""Full path or name of the table-like object to open. This can be a
2838table, table view, shapefile, ArcInfo coverage, geodatabase feature
2839class, feature layer, raster dataset, raster layer, or anything else
2840that has fields and that may be accessed through the ArcGIS
2841geoprocessor."""))
2842
2843AddArgumentMetadata(ArcGISTable.__init__, u'autoDeleteFieldAddedByArcGIS',
2844    typeMetadata=BooleanTypeMetadata(),
2845    description=_(
2846u"""If True (the default) and you are opening a shapefile or dBASE
2847table (.DBF file), the "Id" field (if a shapefile) or "Field1" (if a
2848dBASE table) that added by ArcGIS when the shapefile or table was
2849created will be automatically deleted the first time you call the
2850AddField function. This parameter is ignored you open a table or a
2851feature class that is not a shapefile or dBASE table.
2852
2853ArcGIS requires that shapefiles and dBASE tables have at least one
2854field, in addition to the OID and Shape fields. To meet this
2855requirement, ArcGIS automatically adds a field called "Id" to new
2856shapefiles and "Field1" to dBASE tables . The default behavior of
2857ArcGISTable is to automatically delete this field the first time you
2858call AddField on the returned ArcGISTable instance, under the
2859assumption that most people do not want these fields. You can override
2860this behavior by passing False for this parameter."""))
2861
2862CopyArgumentMetadata(CollectibleObject.__init__, u'parentCollection', ArcGISTable.__init__, u'parentCollection')
2863CopyArgumentMetadata(CollectibleObject.__init__, u'queryableAttributeValues', ArcGISTable.__init__, u'queryableAttributeValues')
2864CopyArgumentMetadata(CollectibleObject.__init__, u'lazyPropertyValues', ArcGISTable.__init__, u'lazyPropertyValues')
2865CopyArgumentMetadata(DatasetCollection.__init__, u'cacheDirectory', ArcGISTable.__init__, u'cacheDirectory')     # TODO: Remove?
2866
2867AddResultMetadata(ArcGISTable.__init__, u'table',
2868    typeMetadata=ClassInstanceTypeMetadata(cls=ArcGISTable),
2869    description=_(
2870u"""An ArcGISTable instance representing the object."""))
2871
2872# TODO: Add metadata
2873
2874###############################################################################
2875# Names exported by this module
2876###############################################################################
2877
2878__all__ = ['ArcGISWorkspace', 'ArcGISRaster', 'ArcGISRasterBand', 'ArcGISTable', 'ArcGISSelectCursor', 'ArcGISUpdateCursor', 'ArcGISInsertCursor']
Note: See TracBrowser for help on using the browser.