web/lib/django/contrib/gis/utils/layermapping.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     1 # LayerMapping -- A Django Model/OGR Layer Mapping Utility
     1 # LayerMapping -- A Django Model/OGR Layer Mapping Utility
     2 """
     2 """
     3  The LayerMapping class provides a way to map the contents of OGR
     3  The LayerMapping class provides a way to map the contents of OGR
     4  vector files (e.g. SHP files) to Geographic-enabled Django models.
     4  vector files (e.g. SHP files) to Geographic-enabled Django models.
     5 
     5 
     6  This grew out of my personal needs, specifically the code repetition
     6  For more information, please consult the GeoDjango documentation:
     7  that went into pulling geometries and fields out of an OGR layer,
     7    http://geodjango.org/docs/layermapping.html
     8  converting to another coordinate system (e.g. WGS84), and then inserting
       
     9  into a GeoDjango model.
       
    10 
       
    11  Please report any bugs encountered using this utility.
       
    12 
       
    13  Requirements:  OGR C Library (from GDAL) required.
       
    14 
       
    15  Usage: 
       
    16   lm = LayerMapping(model, source_file, mapping) where,
       
    17 
       
    18   model:
       
    19    GeoDjango model (not an instance)
       
    20 
       
    21   data:
       
    22    OGR-supported data source file (e.g. a shapefile) or
       
    23     gdal.DataSource instance
       
    24 
       
    25   mapping:
       
    26    A python dictionary, keys are strings corresponding
       
    27    to the GeoDjango model field, and values correspond to
       
    28    string field names for the OGR feature, or if the model field
       
    29    is a geographic then it should correspond to the OGR
       
    30    geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
       
    31 
       
    32  Keyword Args:
       
    33   layer:
       
    34    The index of the layer to use from the Data Source (defaults to 0)
       
    35 
       
    36   source_srs:
       
    37    Use this to specify the source SRS manually (for example, 
       
    38    some shapefiles don't come with a '.prj' file).  An integer SRID,
       
    39    a string WKT, and SpatialReference objects are valid parameters.
       
    40 
       
    41   encoding:
       
    42    Specifies the encoding of the string in the OGR data source.
       
    43    For example, 'latin-1', 'utf-8', and 'cp437' are all valid
       
    44    encoding parameters.
       
    45 
       
    46   transaction_mode:
       
    47    May be 'commit_on_success' (default) or 'autocommit'.
       
    48 
       
    49   transform:
       
    50    Setting this to False will disable all coordinate transformations.  
       
    51 
       
    52   unique:
       
    53    Setting this to the name, or a tuple of names, from the given
       
    54    model will create models unique only to the given name(s).
       
    55    Geometries will from each feature will be added into the collection
       
    56    associated with the unique model.  Forces transaction mode to
       
    57    be 'autocommit'.
       
    58 
       
    59 Example:
       
    60 
       
    61  1. You need a GDAL-supported data source, like a shapefile.
       
    62 
       
    63   Assume we're using the test_poly SHP file:
       
    64   >>> from django.contrib.gis.gdal import DataSource
       
    65   >>> ds = DataSource('test_poly.shp')
       
    66   >>> layer = ds[0]
       
    67   >>> print layer.fields # Exploring the fields in the layer, we only want the 'str' field.
       
    68   ['float', 'int', 'str']
       
    69   >>> print len(layer) # getting the number of features in the layer (should be 3)
       
    70   3
       
    71   >>> print layer.geom_type # Should be 3 (a Polygon)
       
    72   3
       
    73   >>> print layer.srs # WGS84
       
    74   GEOGCS["GCS_WGS_1984",
       
    75       DATUM["WGS_1984",
       
    76           SPHEROID["WGS_1984",6378137,298.257223563]],
       
    77       PRIMEM["Greenwich",0],
       
    78       UNIT["Degree",0.017453292519943295]]
       
    79 
       
    80  2. Now we define our corresponding Django model (make sure to use syncdb):
       
    81 
       
    82   from django.contrib.gis.db import models
       
    83   class TestGeo(models.Model, models.GeoMixin):
       
    84       name = models.CharField(maxlength=25) # corresponds to the 'str' field
       
    85       poly = models.PolygonField(srid=4269) # we want our model in a different SRID
       
    86       objects = models.GeoManager()
       
    87       def __str__(self):
       
    88           return 'Name: %s' % self.name
       
    89 
       
    90  3. Use LayerMapping to extract all the features and place them in the database:
       
    91 
       
    92   >>> from django.contrib.gis.utils import LayerMapping
       
    93   >>> from geoapp.models import TestGeo
       
    94   >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' layer field.
       
    95                  'poly' : 'POLYGON', # For geometry fields use OGC name.
       
    96                  } # The mapping is a dictionary
       
    97   >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) 
       
    98   >>> lm.save(verbose=True) # Save the layermap, imports the data. 
       
    99   Saved: Name: 1
       
   100   Saved: Name: 2
       
   101   Saved: Name: 3
       
   102 
       
   103  LayerMapping just transformed the three geometries from the SHP file from their
       
   104  source spatial reference system (WGS84) to the spatial reference system of
       
   105  the GeoDjango model (NAD83).  If no spatial reference system is defined for
       
   106  the layer, use the `source_srs` keyword with a SpatialReference object to
       
   107  specify one.
       
   108 """
     8 """
   109 import sys
     9 import sys
   110 from datetime import date, datetime
    10 from datetime import date, datetime
   111 from decimal import Decimal
    11 from decimal import Decimal
   112 from django.core.exceptions import ObjectDoesNotExist
    12 from django.core.exceptions import ObjectDoesNotExist
       
    13 from django.db import connections, DEFAULT_DB_ALIAS
   113 from django.contrib.gis.db.models import GeometryField
    14 from django.contrib.gis.db.models import GeometryField
   114 from django.contrib.gis.db.backend import SpatialBackend
       
   115 from django.contrib.gis.gdal import CoordTransform, DataSource, \
    15 from django.contrib.gis.gdal import CoordTransform, DataSource, \
   116     OGRException, OGRGeometry, OGRGeomType, SpatialReference
    16     OGRException, OGRGeometry, OGRGeomType, SpatialReference
   117 from django.contrib.gis.gdal.field import \
    17 from django.contrib.gis.gdal.field import \
   118     OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
    18     OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
   119 from django.db import models, transaction
    19 from django.db import models, transaction
   126 class InvalidInteger(LayerMapError): pass
    26 class InvalidInteger(LayerMapError): pass
   127 class MissingForeignKey(LayerMapError): pass
    27 class MissingForeignKey(LayerMapError): pass
   128 
    28 
   129 class LayerMapping(object):
    29 class LayerMapping(object):
   130     "A class that maps OGR Layers to GeoDjango Models."
    30     "A class that maps OGR Layers to GeoDjango Models."
   131     
    31 
   132     # Acceptable 'base' types for a multi-geometry type.
    32     # Acceptable 'base' types for a multi-geometry type.
   133     MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
    33     MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
   134                    2 : OGRGeomType('MultiLineString'),
    34                    2 : OGRGeomType('MultiLineString'),
   135                    3 : OGRGeomType('MultiPolygon'),
    35                    3 : OGRGeomType('MultiPolygon'),
       
    36                    OGRGeomType('Point25D').num : OGRGeomType('MultiPoint25D'),
       
    37                    OGRGeomType('LineString25D').num : OGRGeomType('MultiLineString25D'),
       
    38                    OGRGeomType('Polygon25D').num : OGRGeomType('MultiPolygon25D'),
   136                    }
    39                    }
   137 
    40 
   138     # Acceptable Django field types and corresponding acceptable OGR
    41     # Acceptable Django field types and corresponding acceptable OGR
   139     # counterparts.
    42     # counterparts.
   140     FIELD_TYPES = {
    43     FIELD_TYPES = {
   159     # The acceptable transaction modes.
    62     # The acceptable transaction modes.
   160     TRANSACTION_MODES = {'autocommit' : transaction.autocommit,
    63     TRANSACTION_MODES = {'autocommit' : transaction.autocommit,
   161                          'commit_on_success' : transaction.commit_on_success,
    64                          'commit_on_success' : transaction.commit_on_success,
   162                          }
    65                          }
   163 
    66 
   164     def __init__(self, model, data, mapping, layer=0, 
    67     def __init__(self, model, data, mapping, layer=0,
   165                  source_srs=None, encoding=None,
    68                  source_srs=None, encoding=None,
   166                  transaction_mode='commit_on_success', 
    69                  transaction_mode='commit_on_success',
   167                  transform=True, unique=None):
    70                  transform=True, unique=None, using=DEFAULT_DB_ALIAS):
   168         """
    71         """
   169         A LayerMapping object is initialized using the given Model (not an instance),
    72         A LayerMapping object is initialized using the given Model (not an instance),
   170         a DataSource (or string path to an OGR-supported data file), and a mapping
    73         a DataSource (or string path to an OGR-supported data file), and a mapping
   171         dictionary.  See the module level docstring for more details and keyword
    74         dictionary.  See the module level docstring for more details and keyword
   172         argument usage.
    75         argument usage.
   176             self.ds = DataSource(data)
    79             self.ds = DataSource(data)
   177         else:
    80         else:
   178             self.ds = data
    81             self.ds = data
   179         self.layer = self.ds[layer]
    82         self.layer = self.ds[layer]
   180 
    83 
       
    84         self.using = using
       
    85         self.spatial_backend = connections[using].ops
       
    86 
   181         # Setting the mapping & model attributes.
    87         # Setting the mapping & model attributes.
   182         self.mapping = mapping
    88         self.mapping = mapping
   183         self.model = model
    89         self.model = model
   184  
    90 
   185         # Checking the layer -- intitialization of the object will fail if
    91         # Checking the layer -- intitialization of the object will fail if
   186         # things don't check out before hand.
    92         # things don't check out before hand.
   187         self.check_layer()
    93         self.check_layer()
   188 
    94 
   189         # Getting the geometry column associated with the model (an 
    95         # Getting the geometry column associated with the model (an
   190         # exception will be raised if there is no geometry column).
    96         # exception will be raised if there is no geometry column).
   191         if SpatialBackend.mysql:
    97         if self.spatial_backend.mysql:
   192             transform = False
    98             transform = False
   193         else:
    99         else:
   194             self.geo_col = self.geometry_column()
   100             self.geo_field = self.geometry_field()
   195 
   101 
   196         # Checking the source spatial reference system, and getting
   102         # Checking the source spatial reference system, and getting
   197         # the coordinate transformation object (unless the `transform`
   103         # the coordinate transformation object (unless the `transform`
   198         # keyword is set to False)
   104         # keyword is set to False)
   199         if transform:
   105         if transform:
   217             transaction_mode = 'autocommit' # Has to be set to autocommit.
   123             transaction_mode = 'autocommit' # Has to be set to autocommit.
   218             self.unique = unique
   124             self.unique = unique
   219         else:
   125         else:
   220             self.unique = None
   126             self.unique = None
   221 
   127 
   222         # Setting the transaction decorator with the function in the 
   128         # Setting the transaction decorator with the function in the
   223         # transaction modes dictionary.
   129         # transaction modes dictionary.
   224         if transaction_mode in self.TRANSACTION_MODES:
   130         if transaction_mode in self.TRANSACTION_MODES:
   225             self.transaction_decorator = self.TRANSACTION_MODES[transaction_mode]
   131             self.transaction_decorator = self.TRANSACTION_MODES[transaction_mode]
   226             self.transaction_mode = transaction_mode
   132             self.transaction_mode = transaction_mode
   227         else:
   133         else:
   228             raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
   134             raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
   229     
   135 
       
   136         if using is None:
       
   137             pass
       
   138 
   230     #### Checking routines used during initialization ####
   139     #### Checking routines used during initialization ####
   231     def check_fid_range(self, fid_range):
   140     def check_fid_range(self, fid_range):
   232         "This checks the `fid_range` keyword."
   141         "This checks the `fid_range` keyword."
   233         if fid_range:
   142         if fid_range:
   234             if isinstance(fid_range, (tuple, list)):
   143             if isinstance(fid_range, (tuple, list)):
   280 
   189 
   281             if isinstance(model_field, GeometryField):
   190             if isinstance(model_field, GeometryField):
   282                 if self.geom_field:
   191                 if self.geom_field:
   283                     raise LayerMapError('LayerMapping does not support more than one GeometryField per model.')
   192                     raise LayerMapError('LayerMapping does not support more than one GeometryField per model.')
   284 
   193 
       
   194                 # Getting the coordinate dimension of the geometry field.
       
   195                 coord_dim = model_field.dim
       
   196 
   285                 try:
   197                 try:
   286                     gtype = OGRGeomType(ogr_name)
   198                     if coord_dim == 3:
       
   199                         gtype = OGRGeomType(ogr_name + '25D')
       
   200                     else:
       
   201                         gtype = OGRGeomType(ogr_name)
   287                 except OGRException:
   202                 except OGRException:
   288                     raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name)
   203                     raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name)
   289 
   204 
   290                 # Making sure that the OGR Layer's Geometry is compatible.
   205                 # Making sure that the OGR Layer's Geometry is compatible.
   291                 ltype = self.layer.geom_type
   206                 ltype = self.layer.geom_type
   292                 if not (gtype == ltype or self.make_multi(ltype, model_field)):
   207                 if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)):
   293                     raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype))
   208                     raise LayerMapError('Invalid mapping geometry; model has %s%s, '
       
   209                                         'layer geometry type is %s.' %
       
   210                                         (fld_name, (coord_dim == 3 and '(dim=3)') or '', ltype))
   294 
   211 
   295                 # Setting the `geom_field` attribute w/the name of the model field
   212                 # Setting the `geom_field` attribute w/the name of the model field
   296                 # that is a Geometry.
   213                 # that is a Geometry.  Also setting the coordinate dimension
       
   214                 # attribute.
   297                 self.geom_field = field_name
   215                 self.geom_field = field_name
       
   216                 self.coord_dim = coord_dim
   298                 fields_val = model_field
   217                 fields_val = model_field
   299             elif isinstance(model_field, models.ForeignKey):
   218             elif isinstance(model_field, models.ForeignKey):
   300                 if isinstance(ogr_name, dict):
   219                 if isinstance(ogr_name, dict):
   301                     # Is every given related model mapping field in the Layer?
   220                     # Is every given related model mapping field in the Layer?
   302                     rel_model = model_field.rel.to
   221                     rel_model = model_field.rel.to
   303                     for rel_name, ogr_field in ogr_name.items(): 
   222                     for rel_name, ogr_field in ogr_name.items():
   304                         idx = check_ogr_fld(ogr_field)
   223                         idx = check_ogr_fld(ogr_field)
   305                         try:
   224                         try:
   306                             rel_field = rel_model._meta.get_field(rel_name)
   225                             rel_field = rel_model._meta.get_field(rel_name)
   307                         except models.fields.FieldDoesNotExist:
   226                         except models.fields.FieldDoesNotExist:
   308                             raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' % 
   227                             raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' %
   309                                                 (rel_name, rel_model.__class__.__name__))
   228                                                 (rel_name, rel_model.__class__.__name__))
   310                     fields_val = rel_model
   229                     fields_val = rel_model
   311                 else:
   230                 else:
   312                     raise TypeError('ForeignKey mapping must be of dictionary type.')
   231                     raise TypeError('ForeignKey mapping must be of dictionary type.')
   313             else:
   232             else:
   319                 idx = check_ogr_fld(ogr_name)
   238                 idx = check_ogr_fld(ogr_name)
   320                 ogr_field = ogr_field_types[idx]
   239                 ogr_field = ogr_field_types[idx]
   321 
   240 
   322                 # Can the OGR field type be mapped to the Django field type?
   241                 # Can the OGR field type be mapped to the Django field type?
   323                 if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]):
   242                 if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]):
   324                     raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' % 
   243                     raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' %
   325                                         (ogr_field, ogr_field.__name__, fld_name))
   244                                         (ogr_field, ogr_field.__name__, fld_name))
   326                 fields_val = model_field
   245                 fields_val = model_field
   327         
   246 
   328             self.fields[field_name] = fields_val
   247             self.fields[field_name] = fields_val
   329 
   248 
   330     def check_srs(self, source_srs):
   249     def check_srs(self, source_srs):
   331         "Checks the compatibility of the given spatial reference object."
   250         "Checks the compatibility of the given spatial reference object."
   332         from django.contrib.gis.models import SpatialRefSys
   251 
   333         if isinstance(source_srs, SpatialReference):
   252         if isinstance(source_srs, SpatialReference):
   334             sr = source_srs
   253             sr = source_srs
   335         elif isinstance(source_srs, SpatialRefSys):
   254         elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()):
   336             sr = source_srs.srs
   255             sr = source_srs.srs
   337         elif isinstance(source_srs, (int, basestring)):
   256         elif isinstance(source_srs, (int, basestring)):
   338             sr = SpatialReference(source_srs)
   257             sr = SpatialReference(source_srs)
   339         else:
   258         else:
   340             # Otherwise just pulling the SpatialReference from the layer
   259             # Otherwise just pulling the SpatialReference from the layer
   341             sr = self.layer.srs
   260             sr = self.layer.srs
   342         
   261 
   343         if not sr:
   262         if not sr:
   344             raise LayerMapError('No source reference system defined.')
   263             raise LayerMapError('No source reference system defined.')
   345         else:
   264         else:
   346             return sr
   265             return sr
   347 
   266 
   348     def check_unique(self, unique):
   267     def check_unique(self, unique):
   349         "Checks the `unique` keyword parameter -- may be a sequence or string."
   268         "Checks the `unique` keyword parameter -- may be a sequence or string."
   350         if isinstance(unique, (list, tuple)):
   269         if isinstance(unique, (list, tuple)):
   351             # List of fields to determine uniqueness with
   270             # List of fields to determine uniqueness with
   352             for attr in unique: 
   271             for attr in unique:
   353                 if not attr in self.mapping: raise ValueError
   272                 if not attr in self.mapping: raise ValueError
   354         elif isinstance(unique, basestring):
   273         elif isinstance(unique, basestring):
   355             # Only a single field passed in.
   274             # Only a single field passed in.
   356             if unique not in self.mapping: raise ValueError
   275             if unique not in self.mapping: raise ValueError
   357         else:
   276         else:
   368 
   287 
   369         # Incrementing through each model field and OGR field in the
   288         # Incrementing through each model field and OGR field in the
   370         # dictionary mapping.
   289         # dictionary mapping.
   371         for field_name, ogr_name in self.mapping.items():
   290         for field_name, ogr_name in self.mapping.items():
   372             model_field = self.fields[field_name]
   291             model_field = self.fields[field_name]
   373             
   292 
   374             if isinstance(model_field, GeometryField):
   293             if isinstance(model_field, GeometryField):
   375                 # Verify OGR geometry.
   294                 # Verify OGR geometry.
   376                 val = self.verify_geom(feat.geom, model_field)
   295                 val = self.verify_geom(feat.geom, model_field)
   377             elif isinstance(model_field, models.base.ModelBase):
   296             elif isinstance(model_field, models.base.ModelBase):
   378                 # The related _model_, not a field was passed in -- indicating
   297                 # The related _model_, not a field was passed in -- indicating
   383                 val = self.verify_ogr_field(feat[ogr_name], model_field)
   302                 val = self.verify_ogr_field(feat[ogr_name], model_field)
   384 
   303 
   385             # Setting the keyword arguments for the field name with the
   304             # Setting the keyword arguments for the field name with the
   386             # value obtained above.
   305             # value obtained above.
   387             kwargs[field_name] = val
   306             kwargs[field_name] = val
   388             
   307 
   389         return kwargs
   308         return kwargs
   390 
   309 
   391     def unique_kwargs(self, kwargs):
   310     def unique_kwargs(self, kwargs):
   392         """
   311         """
   393         Given the feature keyword arguments (from `feature_kwargs`) this routine
   312         Given the feature keyword arguments (from `feature_kwargs`) this routine
   401 
   320 
   402     #### Verification routines used in constructing model keyword arguments. ####
   321     #### Verification routines used in constructing model keyword arguments. ####
   403     def verify_ogr_field(self, ogr_field, model_field):
   322     def verify_ogr_field(self, ogr_field, model_field):
   404         """
   323         """
   405         Verifies if the OGR Field contents are acceptable to the Django
   324         Verifies if the OGR Field contents are acceptable to the Django
   406         model field.  If they are, the verified value is returned, 
   325         model field.  If they are, the verified value is returned,
   407         otherwise the proper exception is raised.
   326         otherwise the proper exception is raised.
   408         """
   327         """
   409         if (isinstance(ogr_field, OFTString) and 
   328         if (isinstance(ogr_field, OFTString) and
   410             isinstance(model_field, (models.CharField, models.TextField))): 
   329             isinstance(model_field, (models.CharField, models.TextField))):
   411             if self.encoding:
   330             if self.encoding:
   412                 # The encoding for OGR data sources may be specified here
   331                 # The encoding for OGR data sources may be specified here
   413                 # (e.g., 'cp437' for Census Bureau boundary files).
   332                 # (e.g., 'cp437' for Census Bureau boundary files).
   414                 val = unicode(ogr_field.value, self.encoding)
   333                 val = unicode(ogr_field.value, self.encoding)
   415             else:
   334             else:
   430             d_idx = dtup[2] # index where the decimal is
   349             d_idx = dtup[2] # index where the decimal is
   431 
   350 
   432             # Maximum amount of precision, or digits to the left of the decimal.
   351             # Maximum amount of precision, or digits to the left of the decimal.
   433             max_prec = model_field.max_digits - model_field.decimal_places
   352             max_prec = model_field.max_digits - model_field.decimal_places
   434 
   353 
   435             # Getting the digits to the left of the decimal place for the 
   354             # Getting the digits to the left of the decimal place for the
   436             # given decimal.
   355             # given decimal.
   437             if d_idx < 0:
   356             if d_idx < 0:
   438                 n_prec = len(digits[:d_idx])
   357                 n_prec = len(digits[:d_idx])
   439             else:
   358             else:
   440                 n_prec = len(digits) + d_idx
   359                 n_prec = len(digits) + d_idx
   441 
   360 
   442             # If we have more than the maximum digits allowed, then throw an 
   361             # If we have more than the maximum digits allowed, then throw an
   443             # InvalidDecimal exception.
   362             # InvalidDecimal exception.
   444             if n_prec > max_prec:
   363             if n_prec > max_prec:
   445                 raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' %
   364                 raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' %
   446                                      (model_field.max_digits, model_field.decimal_places, max_prec))
   365                                      (model_field.max_digits, model_field.decimal_places, max_prec))
   447             val = d
   366             val = d
   460         Given an OGR Feature, the related model and its dictionary mapping,
   379         Given an OGR Feature, the related model and its dictionary mapping,
   461         this routine will retrieve the related model for the ForeignKey
   380         this routine will retrieve the related model for the ForeignKey
   462         mapping.
   381         mapping.
   463         """
   382         """
   464         # TODO: It is expensive to retrieve a model for every record --
   383         # TODO: It is expensive to retrieve a model for every record --
   465         #  explore if an efficient mechanism exists for caching related 
   384         #  explore if an efficient mechanism exists for caching related
   466         #  ForeignKey models.
   385         #  ForeignKey models.
   467 
   386 
   468         # Constructing and verifying the related model keyword arguments.
   387         # Constructing and verifying the related model keyword arguments.
   469         fk_kwargs = {}
   388         fk_kwargs = {}
   470         for field_name, ogr_name in rel_mapping.items():
   389         for field_name, ogr_name in rel_mapping.items():
   473         # Attempting to retrieve and return the related model.
   392         # Attempting to retrieve and return the related model.
   474         try:
   393         try:
   475             return rel_model.objects.get(**fk_kwargs)
   394             return rel_model.objects.get(**fk_kwargs)
   476         except ObjectDoesNotExist:
   395         except ObjectDoesNotExist:
   477             raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs))
   396             raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs))
   478             
   397 
   479     def verify_geom(self, geom, model_field):
   398     def verify_geom(self, geom, model_field):
   480         """
   399         """
   481         Verifies the geometry -- will construct and return a GeometryCollection
   400         Verifies the geometry -- will construct and return a GeometryCollection
   482         if necessary (for example if the model field is MultiPolygonField while
   401         if necessary (for example if the model field is MultiPolygonField while
   483         the mapped shapefile only contains Polygons).
   402         the mapped shapefile only contains Polygons).
   484         """
   403         """
       
   404         # Downgrade a 3D geom to a 2D one, if necessary.
       
   405         if self.coord_dim != geom.coord_dim:
       
   406             geom.coord_dim = self.coord_dim
       
   407 
   485         if self.make_multi(geom.geom_type, model_field):
   408         if self.make_multi(geom.geom_type, model_field):
   486             # Constructing a multi-geometry type to contain the single geometry
   409             # Constructing a multi-geometry type to contain the single geometry
   487             multi_type = self.MULTI_TYPES[geom.geom_type.num]
   410             multi_type = self.MULTI_TYPES[geom.geom_type.num]
   488             g = OGRGeometry(multi_type)
   411             g = OGRGeometry(multi_type)
   489             g.add(geom)
   412             g.add(geom)
   490         else:
   413         else:
   491             g = geom
   414             g = geom
   492 
   415 
   493         # Transforming the geometry with our Coordinate Transformation object,
   416         # Transforming the geometry with our Coordinate Transformation object,
   494         # but only if the class variable `transform` is set w/a CoordTransform 
   417         # but only if the class variable `transform` is set w/a CoordTransform
   495         # object.
   418         # object.
   496         if self.transform: g.transform(self.transform)
   419         if self.transform: g.transform(self.transform)
   497         
   420 
   498         # Returning the WKT of the geometry.
   421         # Returning the WKT of the geometry.
   499         return g.wkt
   422         return g.wkt
   500 
   423 
   501     #### Other model methods ####
   424     #### Other model methods ####
   502     def coord_transform(self):
   425     def coord_transform(self):
   503         "Returns the coordinate transformation object."
   426         "Returns the coordinate transformation object."
   504         from django.contrib.gis.models import SpatialRefSys
   427         SpatialRefSys = self.spatial_backend.spatial_ref_sys()
   505         try:
   428         try:
   506             # Getting the target spatial reference system
   429             # Getting the target spatial reference system
   507             target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
   430             target_srs = SpatialRefSys.objects.get(srid=self.geo_field.srid).srs
   508 
   431 
   509             # Creating the CoordTransform object
   432             # Creating the CoordTransform object
   510             return CoordTransform(self.source_srs, target_srs)
   433             return CoordTransform(self.source_srs, target_srs)
   511         except Exception, msg:
   434         except Exception, msg:
   512             raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
   435             raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
   513 
   436 
   514     def geometry_column(self):
   437     def geometry_field(self):
   515         "Returns the GeometryColumn model associated with the geographic column."
   438         "Returns the GeometryField instance associated with the geographic column."
   516         from django.contrib.gis.models import GeometryColumns
   439         # Use the `get_field_by_name` on the model's options so that we
   517         # Getting the GeometryColumn object.
   440         # get the correct field instance if there's model inheritance.
   518         try:
   441         opts = self.model._meta
   519             db_table = self.model._meta.db_table
   442         fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
   520             geo_col = self.geom_field
   443         return fld
   521             if SpatialBackend.oracle:
       
   522                 # Making upper case for Oracle.
       
   523                 db_table = db_table.upper()
       
   524                 geo_col = geo_col.upper()
       
   525             gc_kwargs = {GeometryColumns.table_name_col() : db_table,
       
   526                          GeometryColumns.geom_col_name() : geo_col,
       
   527                          }
       
   528             return GeometryColumns.objects.get(**gc_kwargs)
       
   529         except Exception, msg:
       
   530             raise LayerMapError('Geometry column does not exist for model. (did you run syncdb?):\n %s' % msg)
       
   531 
   444 
   532     def make_multi(self, geom_type, model_field):
   445     def make_multi(self, geom_type, model_field):
   533         """
   446         """
   534         Given the OGRGeomType for a geometry and its associated GeometryField, 
   447         Given the OGRGeomType for a geometry and its associated GeometryField,
   535         determine whether the geometry should be turned into a GeometryCollection.
   448         determine whether the geometry should be turned into a GeometryCollection.
   536         """
   449         """
   537         return (geom_type.num in self.MULTI_TYPES and 
   450         return (geom_type.num in self.MULTI_TYPES and
   538                 model_field.__class__.__name__ == 'Multi%s' % geom_type.django)
   451                 model_field.__class__.__name__ == 'Multi%s' % geom_type.django)
   539 
   452 
   540     def save(self, verbose=False, fid_range=False, step=False, 
   453     def save(self, verbose=False, fid_range=False, step=False,
   541              progress=False, silent=False, stream=sys.stdout, strict=False):
   454              progress=False, silent=False, stream=sys.stdout, strict=False):
   542         """
   455         """
   543         Saves the contents from the OGR DataSource Layer into the database
   456         Saves the contents from the OGR DataSource Layer into the database
   544         according to the mapping dictionary given at initialization. 
   457         according to the mapping dictionary given at initialization.
   545         
   458 
   546         Keyword Parameters:
   459         Keyword Parameters:
   547          verbose:
   460          verbose:
   548            If set, information will be printed subsequent to each model save 
   461            If set, information will be printed subsequent to each model save
   549            executed on the database.
   462            executed on the database.
   550 
   463 
   551          fid_range:
   464          fid_range:
   552            May be set with a slice or tuple of (begin, end) feature ID's to map
   465            May be set with a slice or tuple of (begin, end) feature ID's to map
   553            from the data source.  In other words, this keyword enables the user
   466            from the data source.  In other words, this keyword enables the user
   554            to selectively import a subset range of features in the geographic
   467            to selectively import a subset range of features in the geographic
   555            data source.
   468            data source.
   556 
   469 
   557          step:
   470          step:
   558            If set with an integer, transactions will occur at every step 
   471            If set with an integer, transactions will occur at every step
   559            interval. For example, if step=1000, a commit would occur after 
   472            interval. For example, if step=1000, a commit would occur after
   560            the 1,000th feature, the 2,000th feature etc.
   473            the 1,000th feature, the 2,000th feature etc.
   561 
   474 
   562          progress:
   475          progress:
   563            When this keyword is set, status information will be printed giving 
   476            When this keyword is set, status information will be printed giving
   564            the number of features processed and sucessfully saved.  By default, 
   477            the number of features processed and sucessfully saved.  By default,
   565            progress information will pe printed every 1000 features processed, 
   478            progress information will pe printed every 1000 features processed,
   566            however, this default may be overridden by setting this keyword with an 
   479            however, this default may be overridden by setting this keyword with an
   567            integer for the desired interval.
   480            integer for the desired interval.
   568 
   481 
   569          stream:
   482          stream:
   570            Status information will be written to this file handle.  Defaults to 
   483            Status information will be written to this file handle.  Defaults to
   571            using `sys.stdout`, but any object with a `write` method is supported.
   484            using `sys.stdout`, but any object with a `write` method is supported.
   572 
   485 
   573          silent:
   486          silent:
   574            By default, non-fatal error notifications are printed to stdout, but 
   487            By default, non-fatal error notifications are printed to stdout, but
   575            this keyword may be set to disable these notifications.
   488            this keyword may be set to disable these notifications.
   576 
   489 
   577          strict:
   490          strict:
   578            Execution of the model mapping will cease upon the first error 
   491            Execution of the model mapping will cease upon the first error
   579            encountered.  The default behavior is to attempt to continue.
   492            encountered.  The default behavior is to attempt to continue.
   580         """
   493         """
   581         # Getting the default Feature ID range.
   494         # Getting the default Feature ID range.
   582         default_range = self.check_fid_range(fid_range)
   495         default_range = self.check_fid_range(fid_range)
   583     
   496 
   584         # Setting the progress interval, if requested.
   497         # Setting the progress interval, if requested.
   585         if progress:
   498         if progress:
   586             if progress is True or not isinstance(progress, int):
   499             if progress is True or not isinstance(progress, int):
   587                 progress_interval = 1000
   500                 progress_interval = 1000
   588             else:
   501             else:
   589                 progress_interval = progress
   502                 progress_interval = progress
   590 
   503 
   591         # Defining the 'real' save method, utilizing the transaction 
   504         # Defining the 'real' save method, utilizing the transaction
   592         # decorator created during initialization.
   505         # decorator created during initialization.
   593         @self.transaction_decorator
   506         @self.transaction_decorator
   594         def _save(feat_range=default_range, num_feat=0, num_saved=0):
   507         def _save(feat_range=default_range, num_feat=0, num_saved=0):
   595             if feat_range:
   508             if feat_range:
   596                 layer_iter = self.layer[feat_range]
   509                 layer_iter = self.layer[feat_range]
   603                 try:
   516                 try:
   604                     kwargs = self.feature_kwargs(feat)
   517                     kwargs = self.feature_kwargs(feat)
   605                 except LayerMapError, msg:
   518                 except LayerMapError, msg:
   606                     # Something borked the validation
   519                     # Something borked the validation
   607                     if strict: raise
   520                     if strict: raise
   608                     elif not silent: 
   521                     elif not silent:
   609                         stream.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg))
   522                         stream.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg))
   610                 else:
   523                 else:
   611                     # Constructing the model using the keyword args
   524                     # Constructing the model using the keyword args
   612                     is_update = False
   525                     is_update = False
   613                     if self.unique:
   526                     if self.unique:
   615                         # geometry appropriately.
   528                         # geometry appropriately.
   616                         try:
   529                         try:
   617                             # Getting the keyword arguments and retrieving
   530                             # Getting the keyword arguments and retrieving
   618                             # the unique model.
   531                             # the unique model.
   619                             u_kwargs = self.unique_kwargs(kwargs)
   532                             u_kwargs = self.unique_kwargs(kwargs)
   620                             m = self.model.objects.get(**u_kwargs)
   533                             m = self.model.objects.using(self.using).get(**u_kwargs)
   621                             is_update = True
   534                             is_update = True
   622                                 
   535 
   623                             # Getting the geometry (in OGR form), creating 
   536                             # Getting the geometry (in OGR form), creating
   624                             # one from the kwargs WKT, adding in additional 
   537                             # one from the kwargs WKT, adding in additional
   625                             # geometries, and update the attribute with the 
   538                             # geometries, and update the attribute with the
   626                             # just-updated geometry WKT.
   539                             # just-updated geometry WKT.
   627                             geom = getattr(m, self.geom_field).ogr
   540                             geom = getattr(m, self.geom_field).ogr
   628                             new = OGRGeometry(kwargs[self.geom_field])
   541                             new = OGRGeometry(kwargs[self.geom_field])
   629                             for g in new: geom.add(g) 
   542                             for g in new: geom.add(g)
   630                             setattr(m, self.geom_field, geom.wkt)
   543                             setattr(m, self.geom_field, geom.wkt)
   631                         except ObjectDoesNotExist:
   544                         except ObjectDoesNotExist:
   632                             # No unique model exists yet, create.
   545                             # No unique model exists yet, create.
   633                             m = self.model(**kwargs)
   546                             m = self.model(**kwargs)
   634                     else:
   547                     else:
   635                         m = self.model(**kwargs)
   548                         m = self.model(**kwargs)
   636 
   549 
   637                     try:
   550                     try:
   638                         # Attempting to save.
   551                         # Attempting to save.
   639                         m.save()
   552                         m.save(using=self.using)
   640                         num_saved += 1
   553                         num_saved += 1
   641                         if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
   554                         if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
   642                     except SystemExit:
   555                     except SystemExit:
   643                         raise
   556                         raise
   644                     except Exception, msg:
   557                     except Exception, msg:
   645                         if self.transaction_mode == 'autocommit':
   558                         if self.transaction_mode == 'autocommit':
   646                             # Rolling back the transaction so that other model saves
   559                             # Rolling back the transaction so that other model saves
   647                             # will work.
   560                             # will work.
   648                             transaction.rollback_unless_managed()
   561                             transaction.rollback_unless_managed()
   649                         if strict: 
   562                         if strict:
   650                             # Bailing out if the `strict` keyword is set.
   563                             # Bailing out if the `strict` keyword is set.
   651                             if not silent:
   564                             if not silent:
   652                                 stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid)
   565                                 stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid)
   653                                 stream.write('%s\n' % kwargs)
   566                                 stream.write('%s\n' % kwargs)
   654                             raise
   567                             raise
   656                             stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg))
   569                             stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg))
   657 
   570 
   658                 # Printing progress information, if requested.
   571                 # Printing progress information, if requested.
   659                 if progress and num_feat % progress_interval == 0:
   572                 if progress and num_feat % progress_interval == 0:
   660                     stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved))
   573                     stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved))
   661         
   574 
   662             # Only used for status output purposes -- incremental saving uses the
   575             # Only used for status output purposes -- incremental saving uses the
   663             # values returned here.
   576             # values returned here.
   664             return num_saved, num_feat
   577             return num_saved, num_feat
   665 
   578 
   666         nfeat = self.layer.num_feat
   579         nfeat = self.layer.num_feat
   667         if step and isinstance(step, int) and step < nfeat:
   580         if step and isinstance(step, int) and step < nfeat:
   668             # Incremental saving is requested at the given interval (step) 
   581             # Incremental saving is requested at the given interval (step)
   669             if default_range: 
   582             if default_range:
   670                 raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.')
   583                 raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.')
   671             beg, num_feat, num_saved = (0, 0, 0)
   584             beg, num_feat, num_saved = (0, 0, 0)
   672             indices = range(step, nfeat, step)
   585             indices = range(step, nfeat, step)
   673             n_i = len(indices)
   586             n_i = len(indices)
   674 
   587 
   675             for i, end in enumerate(indices):
   588             for i, end in enumerate(indices):
   676                 # Constructing the slice to use for this step; the last slice is
   589                 # Constructing the slice to use for this step; the last slice is
   677                 # special (e.g, [100:] instead of [90:100]).
   590                 # special (e.g, [100:] instead of [90:100]).
   678                 if i+1 == n_i: step_slice = slice(beg, None)
   591                 if i+1 == n_i: step_slice = slice(beg, None)
   679                 else: step_slice = slice(beg, end)
   592                 else: step_slice = slice(beg, end)
   680             
   593 
   681                 try:
   594                 try:
   682                     num_feat, num_saved = _save(step_slice, num_feat, num_saved)
   595                     num_feat, num_saved = _save(step_slice, num_feat, num_saved)
   683                     beg = end
   596                     beg = end
   684                 except:
   597                 except:
   685                     stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice))
   598                     stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice))