--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/django/contrib/gis/utils/layermapping.py Tue Oct 14 16:00:59 2008 +0000
@@ -0,0 +1,684 @@
+# LayerMapping -- A Django Model/OGR Layer Mapping Utility
+"""
+ The LayerMapping class provides a way to map the contents of OGR
+ vector files (e.g. SHP files) to Geographic-enabled Django models.
+
+ This grew out of my personal needs, specifically the code repetition
+ that went into pulling geometries and fields out of an OGR layer,
+ converting to another coordinate system (e.g. WGS84), and then inserting
+ into a GeoDjango model.
+
+ Please report any bugs encountered using this utility.
+
+ Requirements: OGR C Library (from GDAL) required.
+
+ Usage:
+ lm = LayerMapping(model, source_file, mapping) where,
+
+ model:
+ GeoDjango model (not an instance)
+
+ data:
+ OGR-supported data source file (e.g. a shapefile) or
+ gdal.DataSource instance
+
+ mapping:
+ A python dictionary, keys are strings corresponding
+ to the GeoDjango model field, and values correspond to
+ string field names for the OGR feature, or if the model field
+ is a geographic then it should correspond to the OGR
+ geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
+
+ Keyword Args:
+ layer:
+ The index of the layer to use from the Data Source (defaults to 0)
+
+ source_srs:
+ Use this to specify the source SRS manually (for example,
+ some shapefiles don't come with a '.prj' file). An integer SRID,
+ a string WKT, and SpatialReference objects are valid parameters.
+
+ encoding:
+ Specifies the encoding of the string in the OGR data source.
+ For example, 'latin-1', 'utf-8', and 'cp437' are all valid
+ encoding parameters.
+
+ transaction_mode:
+ May be 'commit_on_success' (default) or 'autocommit'.
+
+ transform:
+ Setting this to False will disable all coordinate transformations.
+
+ unique:
+ Setting this to the name, or a tuple of names, from the given
+ model will create models unique only to the given name(s).
+ Geometries will from each feature will be added into the collection
+ associated with the unique model. Forces transaction mode to
+ be 'autocommit'.
+
+Example:
+
+ 1. You need a GDAL-supported data source, like a shapefile.
+
+ Assume we're using the test_poly SHP file:
+ >>> from django.contrib.gis.gdal import DataSource
+ >>> ds = DataSource('test_poly.shp')
+ >>> layer = ds[0]
+ >>> print layer.fields # Exploring the fields in the layer, we only want the 'str' field.
+ ['float', 'int', 'str']
+ >>> print len(layer) # getting the number of features in the layer (should be 3)
+ 3
+ >>> print layer.geom_type # Should be 3 (a Polygon)
+ 3
+ >>> print layer.srs # WGS84
+ GEOGCS["GCS_WGS_1984",
+ DATUM["WGS_1984",
+ SPHEROID["WGS_1984",6378137,298.257223563]],
+ PRIMEM["Greenwich",0],
+ UNIT["Degree",0.017453292519943295]]
+
+ 2. Now we define our corresponding Django model (make sure to use syncdb):
+
+ from django.contrib.gis.db import models
+ class TestGeo(models.Model, models.GeoMixin):
+ name = models.CharField(maxlength=25) # corresponds to the 'str' field
+ poly = models.PolygonField(srid=4269) # we want our model in a different SRID
+ objects = models.GeoManager()
+ def __str__(self):
+ return 'Name: %s' % self.name
+
+ 3. Use LayerMapping to extract all the features and place them in the database:
+
+ >>> from django.contrib.gis.utils import LayerMapping
+ >>> from geoapp.models import TestGeo
+ >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' layer field.
+ 'poly' : 'POLYGON', # For geometry fields use OGC name.
+ } # The mapping is a dictionary
+ >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
+ >>> lm.save(verbose=True) # Save the layermap, imports the data.
+ Saved: Name: 1
+ Saved: Name: 2
+ Saved: Name: 3
+
+ LayerMapping just transformed the three geometries from the SHP file from their
+ source spatial reference system (WGS84) to the spatial reference system of
+ the GeoDjango model (NAD83). If no spatial reference system is defined for
+ the layer, use the `source_srs` keyword with a SpatialReference object to
+ specify one.
+"""
+import sys
+from datetime import date, datetime
+from decimal import Decimal
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.gis.db.models import GeometryField
+from django.contrib.gis.db.backend import SpatialBackend
+from django.contrib.gis.gdal import CoordTransform, DataSource, \
+ OGRException, OGRGeometry, OGRGeomType, SpatialReference
+from django.contrib.gis.gdal.field import \
+ OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
+from django.contrib.gis.models import GeometryColumns, SpatialRefSys
+from django.db import models, transaction
+from django.contrib.localflavor.us.models import USStateField
+
+# LayerMapping exceptions.
+class LayerMapError(Exception): pass
+class InvalidString(LayerMapError): pass
+class InvalidDecimal(LayerMapError): pass
+class InvalidInteger(LayerMapError): pass
+class MissingForeignKey(LayerMapError): pass
+
+class LayerMapping(object):
+ "A class that maps OGR Layers to GeoDjango Models."
+
+ # Acceptable 'base' types for a multi-geometry type.
+ MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
+ 2 : OGRGeomType('MultiLineString'),
+ 3 : OGRGeomType('MultiPolygon'),
+ }
+
+ # Acceptable Django field types and corresponding acceptable OGR
+ # counterparts.
+ FIELD_TYPES = {
+ models.AutoField : OFTInteger,
+ models.IntegerField : (OFTInteger, OFTReal, OFTString),
+ models.FloatField : (OFTInteger, OFTReal),
+ models.DateField : OFTDate,
+ models.DateTimeField : OFTDateTime,
+ models.EmailField : OFTString,
+ models.TimeField : OFTTime,
+ models.DecimalField : (OFTInteger, OFTReal),
+ models.CharField : OFTString,
+ models.SlugField : OFTString,
+ models.TextField : OFTString,
+ models.URLField : OFTString,
+ USStateField : OFTString,
+ models.XMLField : OFTString,
+ models.SmallIntegerField : (OFTInteger, OFTReal, OFTString),
+ models.PositiveSmallIntegerField : (OFTInteger, OFTReal, OFTString),
+ }
+
+ # The acceptable transaction modes.
+ TRANSACTION_MODES = {'autocommit' : transaction.autocommit,
+ 'commit_on_success' : transaction.commit_on_success,
+ }
+
+ def __init__(self, model, data, mapping, layer=0,
+ source_srs=None, encoding=None,
+ transaction_mode='commit_on_success',
+ transform=True, unique=None):
+ """
+ A LayerMapping object is initialized using the given Model (not an instance),
+ a DataSource (or string path to an OGR-supported data file), and a mapping
+ dictionary. See the module level docstring for more details and keyword
+ argument usage.
+ """
+ # Getting the DataSource and the associated Layer.
+ if isinstance(data, basestring):
+ self.ds = DataSource(data)
+ else:
+ self.ds = data
+ self.layer = self.ds[layer]
+
+ # Setting the mapping & model attributes.
+ self.mapping = mapping
+ self.model = model
+
+ # Checking the layer -- intitialization of the object will fail if
+ # things don't check out before hand.
+ self.check_layer()
+
+ # Getting the geometry column associated with the model (an
+ # exception will be raised if there is no geometry column).
+ self.geo_col = self.geometry_column()
+
+ # Checking the source spatial reference system, and getting
+ # the coordinate transformation object (unless the `transform`
+ # keyword is set to False)
+ if transform:
+ self.source_srs = self.check_srs(source_srs)
+ self.transform = self.coord_transform()
+ else:
+ self.transform = transform
+
+ # Setting the encoding for OFTString fields, if specified.
+ if encoding:
+ # Making sure the encoding exists, if not a LookupError
+ # exception will be thrown.
+ from codecs import lookup
+ lookup(encoding)
+ self.encoding = encoding
+ else:
+ self.encoding = None
+
+ if unique:
+ self.check_unique(unique)
+ transaction_mode = 'autocommit' # Has to be set to autocommit.
+ self.unique = unique
+ else:
+ self.unique = None
+
+ # Setting the transaction decorator with the function in the
+ # transaction modes dictionary.
+ if transaction_mode in self.TRANSACTION_MODES:
+ self.transaction_decorator = self.TRANSACTION_MODES[transaction_mode]
+ self.transaction_mode = transaction_mode
+ else:
+ raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
+
+ #### Checking routines used during initialization ####
+ def check_fid_range(self, fid_range):
+ "This checks the `fid_range` keyword."
+ if fid_range:
+ if isinstance(fid_range, (tuple, list)):
+ return slice(*fid_range)
+ elif isinstance(fid_range, slice):
+ return fid_range
+ else:
+ raise TypeError
+ else:
+ return None
+
+ def check_layer(self):
+ """
+ This checks the Layer metadata, and ensures that it is compatible
+ with the mapping information and model. Unlike previous revisions,
+ there is no need to increment through each feature in the Layer.
+ """
+ # The geometry field of the model is set here.
+ # TODO: Support more than one geometry field / model. However, this
+ # depends on the GDAL Driver in use.
+ self.geom_field = False
+ self.fields = {}
+
+ # Getting lists of the field names and the field types available in
+ # the OGR Layer.
+ ogr_fields = self.layer.fields
+ ogr_field_types = self.layer.field_types
+
+ # Function for determining if the OGR mapping field is in the Layer.
+ def check_ogr_fld(ogr_map_fld):
+ try:
+ idx = ogr_fields.index(ogr_map_fld)
+ except ValueError:
+ raise LayerMapError('Given mapping OGR field "%s" not found in OGR Layer.' % ogr_map_fld)
+ return idx
+
+ # No need to increment through each feature in the model, simply check
+ # the Layer metadata against what was given in the mapping dictionary.
+ for field_name, ogr_name in self.mapping.items():
+ # Ensuring that a corresponding field exists in the model
+ # for the given field name in the mapping.
+ try:
+ model_field = self.model._meta.get_field(field_name)
+ except models.fields.FieldDoesNotExist:
+ raise LayerMapError('Given mapping field "%s" not in given Model fields.' % field_name)
+
+ # Getting the string name for the Django field class (e.g., 'PointField').
+ fld_name = model_field.__class__.__name__
+
+ if isinstance(model_field, GeometryField):
+ if self.geom_field:
+ raise LayerMapError('LayerMapping does not support more than one GeometryField per model.')
+
+ try:
+ gtype = OGRGeomType(ogr_name)
+ except OGRException:
+ raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name)
+
+ # Making sure that the OGR Layer's Geometry is compatible.
+ ltype = self.layer.geom_type
+ if not (gtype == ltype or self.make_multi(ltype, model_field)):
+ raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype))
+
+ # Setting the `geom_field` attribute w/the name of the model field
+ # that is a Geometry.
+ self.geom_field = field_name
+ fields_val = model_field
+ elif isinstance(model_field, models.ForeignKey):
+ if isinstance(ogr_name, dict):
+ # Is every given related model mapping field in the Layer?
+ rel_model = model_field.rel.to
+ for rel_name, ogr_field in ogr_name.items():
+ idx = check_ogr_fld(ogr_field)
+ try:
+ rel_field = rel_model._meta.get_field(rel_name)
+ except models.fields.FieldDoesNotExist:
+ raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' %
+ (rel_name, rel_model.__class__.__name__))
+ fields_val = rel_model
+ else:
+ raise TypeError('ForeignKey mapping must be of dictionary type.')
+ else:
+ # Is the model field type supported by LayerMapping?
+ if not model_field.__class__ in self.FIELD_TYPES:
+ raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % fld_name)
+
+ # Is the OGR field in the Layer?
+ idx = check_ogr_fld(ogr_name)
+ ogr_field = ogr_field_types[idx]
+
+ # Can the OGR field type be mapped to the Django field type?
+ if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]):
+ raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' %
+ (ogr_field, ogr_field.__name__, fld_name))
+ fields_val = model_field
+
+ self.fields[field_name] = fields_val
+
+ def check_srs(self, source_srs):
+ "Checks the compatibility of the given spatial reference object."
+ if isinstance(source_srs, SpatialReference):
+ sr = source_srs
+ elif isinstance(source_srs, SpatialRefSys):
+ sr = source_srs.srs
+ elif isinstance(source_srs, (int, basestring)):
+ sr = SpatialReference(source_srs)
+ else:
+ # Otherwise just pulling the SpatialReference from the layer
+ sr = self.layer.srs
+
+ if not sr:
+ raise LayerMapError('No source reference system defined.')
+ else:
+ return sr
+
+ def check_unique(self, unique):
+ "Checks the `unique` keyword parameter -- may be a sequence or string."
+ if isinstance(unique, (list, tuple)):
+ # List of fields to determine uniqueness with
+ for attr in unique:
+ if not attr in self.mapping: raise ValueError
+ elif isinstance(unique, basestring):
+ # Only a single field passed in.
+ if unique not in self.mapping: raise ValueError
+ else:
+ raise TypeError('Unique keyword argument must be set with a tuple, list, or string.')
+
+ #### Keyword argument retrieval routines ####
+ def feature_kwargs(self, feat):
+ """
+ Given an OGR Feature, this will return a dictionary of keyword arguments
+ for constructing the mapped model.
+ """
+ # The keyword arguments for model construction.
+ kwargs = {}
+
+ # Incrementing through each model field and OGR field in the
+ # dictionary mapping.
+ for field_name, ogr_name in self.mapping.items():
+ model_field = self.fields[field_name]
+
+ if isinstance(model_field, GeometryField):
+ # Verify OGR geometry.
+ val = self.verify_geom(feat.geom, model_field)
+ elif isinstance(model_field, models.base.ModelBase):
+ # The related _model_, not a field was passed in -- indicating
+ # another mapping for the related Model.
+ val = self.verify_fk(feat, model_field, ogr_name)
+ else:
+ # Otherwise, verify OGR Field type.
+ val = self.verify_ogr_field(feat[ogr_name], model_field)
+
+ # Setting the keyword arguments for the field name with the
+ # value obtained above.
+ kwargs[field_name] = val
+
+ return kwargs
+
+ def unique_kwargs(self, kwargs):
+ """
+ Given the feature keyword arguments (from `feature_kwargs`) this routine
+ will construct and return the uniqueness keyword arguments -- a subset
+ of the feature kwargs.
+ """
+ if isinstance(self.unique, basestring):
+ return {self.unique : kwargs[self.unique]}
+ else:
+ return dict((fld, kwargs[fld]) for fld in self.unique)
+
+ #### Verification routines used in constructing model keyword arguments. ####
+ def verify_ogr_field(self, ogr_field, model_field):
+ """
+ Verifies if the OGR Field contents are acceptable to the Django
+ model field. If they are, the verified value is returned,
+ otherwise the proper exception is raised.
+ """
+ if (isinstance(ogr_field, OFTString) and
+ isinstance(model_field, (models.CharField, models.TextField))):
+ if self.encoding:
+ # The encoding for OGR data sources may be specified here
+ # (e.g., 'cp437' for Census Bureau boundary files).
+ val = unicode(ogr_field.value, self.encoding)
+ else:
+ val = ogr_field.value
+ if len(val) > model_field.max_length:
+ raise InvalidString('%s model field maximum string length is %s, given %s characters.' %
+ (model_field.name, model_field.max_length, len(val)))
+ elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField):
+ try:
+ # Creating an instance of the Decimal value to use.
+ d = Decimal(str(ogr_field.value))
+ except:
+ raise InvalidDecimal('Could not construct decimal from: %s' % ogr_field.value)
+
+ # Getting the decimal value as a tuple.
+ dtup = d.as_tuple()
+ digits = dtup[1]
+ d_idx = dtup[2] # index where the decimal is
+
+ # Maximum amount of precision, or digits to the left of the decimal.
+ max_prec = model_field.max_digits - model_field.decimal_places
+
+ # Getting the digits to the left of the decimal place for the
+ # given decimal.
+ if d_idx < 0:
+ n_prec = len(digits[:d_idx])
+ else:
+ n_prec = len(digits) + d_idx
+
+ # If we have more than the maximum digits allowed, then throw an
+ # InvalidDecimal exception.
+ if n_prec > max_prec:
+ raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' %
+ (model_field.max_digits, model_field.decimal_places, max_prec))
+ val = d
+ elif isinstance(ogr_field, (OFTReal, OFTString)) and isinstance(model_field, models.IntegerField):
+ # Attempt to convert any OFTReal and OFTString value to an OFTInteger.
+ try:
+ val = int(ogr_field.value)
+ except:
+ raise InvalidInteger('Could not construct integer from: %s' % ogr_field.value)
+ else:
+ val = ogr_field.value
+ return val
+
+ def verify_fk(self, feat, rel_model, rel_mapping):
+ """
+ Given an OGR Feature, the related model and its dictionary mapping,
+ this routine will retrieve the related model for the ForeignKey
+ mapping.
+ """
+ # TODO: It is expensive to retrieve a model for every record --
+ # explore if an efficient mechanism exists for caching related
+ # ForeignKey models.
+
+ # Constructing and verifying the related model keyword arguments.
+ fk_kwargs = {}
+ for field_name, ogr_name in rel_mapping.items():
+ fk_kwargs[field_name] = self.verify_ogr_field(feat[ogr_name], rel_model._meta.get_field(field_name))
+
+ # Attempting to retrieve and return the related model.
+ try:
+ return rel_model.objects.get(**fk_kwargs)
+ except ObjectDoesNotExist:
+ raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs))
+
+ def verify_geom(self, geom, model_field):
+ """
+ Verifies the geometry -- will construct and return a GeometryCollection
+ if necessary (for example if the model field is MultiPolygonField while
+ the mapped shapefile only contains Polygons).
+ """
+ if self.make_multi(geom.geom_type, model_field):
+ # Constructing a multi-geometry type to contain the single geometry
+ multi_type = self.MULTI_TYPES[geom.geom_type.num]
+ g = OGRGeometry(multi_type)
+ g.add(geom)
+ else:
+ g = geom
+
+ # Transforming the geometry with our Coordinate Transformation object,
+ # but only if the class variable `transform` is set w/a CoordTransform
+ # object.
+ if self.transform: g.transform(self.transform)
+
+ # Returning the WKT of the geometry.
+ return g.wkt
+
+ #### Other model methods ####
+ def coord_transform(self):
+ "Returns the coordinate transformation object."
+ try:
+ # Getting the target spatial reference system
+ target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
+
+ # Creating the CoordTransform object
+ return CoordTransform(self.source_srs, target_srs)
+ except Exception, msg:
+ raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
+
+ def geometry_column(self):
+ "Returns the GeometryColumn model associated with the geographic column."
+ # Getting the GeometryColumn object.
+ try:
+ db_table = self.model._meta.db_table
+ geo_col = self.geom_field
+ if SpatialBackend.name == 'oracle':
+ # Making upper case for Oracle.
+ db_table = db_table.upper()
+ geo_col = geo_col.upper()
+ gc_kwargs = {GeometryColumns.table_name_col() : db_table,
+ GeometryColumns.geom_col_name() : geo_col,
+ }
+ return GeometryColumns.objects.get(**gc_kwargs)
+ except Exception, msg:
+ raise LayerMapError('Geometry column does not exist for model. (did you run syncdb?):\n %s' % msg)
+
+ def make_multi(self, geom_type, model_field):
+ """
+ Given the OGRGeomType for a geometry and its associated GeometryField,
+ determine whether the geometry should be turned into a GeometryCollection.
+ """
+ return (geom_type.num in self.MULTI_TYPES and
+ model_field.__class__.__name__ == 'Multi%s' % geom_type.django)
+
+ def save(self, verbose=False, fid_range=False, step=False,
+ progress=False, silent=False, stream=sys.stdout, strict=False):
+ """
+ Saves the contents from the OGR DataSource Layer into the database
+ according to the mapping dictionary given at initialization.
+
+ Keyword Parameters:
+ verbose:
+ If set, information will be printed subsequent to each model save
+ executed on the database.
+
+ fid_range:
+ May be set with a slice or tuple of (begin, end) feature ID's to map
+ from the data source. In other words, this keyword enables the user
+ to selectively import a subset range of features in the geographic
+ data source.
+
+ step:
+ If set with an integer, transactions will occur at every step
+ interval. For example, if step=1000, a commit would occur after
+ the 1,000th feature, the 2,000th feature etc.
+
+ progress:
+ When this keyword is set, status information will be printed giving
+ the number of features processed and sucessfully saved. By default,
+ progress information will pe printed every 1000 features processed,
+ however, this default may be overridden by setting this keyword with an
+ integer for the desired interval.
+
+ stream:
+ Status information will be written to this file handle. Defaults to
+ using `sys.stdout`, but any object with a `write` method is supported.
+
+ silent:
+ By default, non-fatal error notifications are printed to stdout, but
+ this keyword may be set to disable these notifications.
+
+ strict:
+ Execution of the model mapping will cease upon the first error
+ encountered. The default behavior is to attempt to continue.
+ """
+ # Getting the default Feature ID range.
+ default_range = self.check_fid_range(fid_range)
+
+ # Setting the progress interval, if requested.
+ if progress:
+ if progress is True or not isinstance(progress, int):
+ progress_interval = 1000
+ else:
+ progress_interval = progress
+
+ # Defining the 'real' save method, utilizing the transaction
+ # decorator created during initialization.
+ @self.transaction_decorator
+ def _save(feat_range=default_range, num_feat=0, num_saved=0):
+ if feat_range:
+ layer_iter = self.layer[feat_range]
+ else:
+ layer_iter = self.layer
+
+ for feat in layer_iter:
+ num_feat += 1
+ # Getting the keyword arguments
+ try:
+ kwargs = self.feature_kwargs(feat)
+ except LayerMapError, msg:
+ # Something borked the validation
+ if strict: raise
+ elif not silent:
+ stream.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg))
+ else:
+ # Constructing the model using the keyword args
+ is_update = False
+ if self.unique:
+ # If we want unique models on a particular field, handle the
+ # geometry appropriately.
+ try:
+ # Getting the keyword arguments and retrieving
+ # the unique model.
+ u_kwargs = self.unique_kwargs(kwargs)
+ m = self.model.objects.get(**u_kwargs)
+ is_update = True
+
+ # Getting the geometry (in OGR form), creating
+ # one from the kwargs WKT, adding in additional
+ # geometries, and update the attribute with the
+ # just-updated geometry WKT.
+ geom = getattr(m, self.geom_field).ogr
+ new = OGRGeometry(kwargs[self.geom_field])
+ for g in new: geom.add(g)
+ setattr(m, self.geom_field, geom.wkt)
+ except ObjectDoesNotExist:
+ # No unique model exists yet, create.
+ m = self.model(**kwargs)
+ else:
+ m = self.model(**kwargs)
+
+ try:
+ # Attempting to save.
+ m.save()
+ num_saved += 1
+ if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
+ except SystemExit:
+ raise
+ except Exception, msg:
+ if self.transaction_mode == 'autocommit':
+ # Rolling back the transaction so that other model saves
+ # will work.
+ transaction.rollback_unless_managed()
+ if strict:
+ # Bailing out if the `strict` keyword is set.
+ if not silent:
+ stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid)
+ stream.write('%s\n' % kwargs)
+ raise
+ elif not silent:
+ stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg))
+
+ # Printing progress information, if requested.
+ if progress and num_feat % progress_interval == 0:
+ stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved))
+
+ # Only used for status output purposes -- incremental saving uses the
+ # values returned here.
+ return num_saved, num_feat
+
+ nfeat = self.layer.num_feat
+ if step and isinstance(step, int) and step < nfeat:
+ # Incremental saving is requested at the given interval (step)
+ if default_range:
+ raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.')
+ beg, num_feat, num_saved = (0, 0, 0)
+ indices = range(step, nfeat, step)
+ n_i = len(indices)
+
+ for i, end in enumerate(indices):
+ # Constructing the slice to use for this step; the last slice is
+ # special (e.g, [100:] instead of [90:100]).
+ if i+1 == n_i: step_slice = slice(beg, None)
+ else: step_slice = slice(beg, end)
+
+ try:
+ num_feat, num_saved = _save(step_slice, num_feat, num_saved)
+ beg = end
+ except:
+ stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice))
+ raise
+ else:
+ # Otherwise, just calling the previously defined _save() function.
+ _save()