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

Revision 895, 149.3 KB (checked in by jjr8, 16 months ago)

* Added SpatialList? support
* Modified Aviso tools to allow the caller to seleect the temporal resolution of the DT datasets.

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