Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

gis: `LayerMapping`: Added the `unique` keyword parameter and tests f…

…or the `transform` keyword and geometry collection conversion.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6980 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ef0f46f1d0b9e41c811d3d6b833f1652ad46aec3 1 parent 387e6cc
Justin Bronn authored December 29, 2007
BIN  django/contrib/gis/tests/layermap/counties/counties.dbf
Binary file not shown
BIN  django/contrib/gis/tests/layermap/counties/counties.shp
Binary file not shown
BIN  django/contrib/gis/tests/layermap/counties/counties.shx
Binary file not shown
23  django/contrib/gis/tests/layermap/models.py
... ...
@@ -1,5 +1,15 @@
1 1
 from django.contrib.gis.db import models
2 2
 
  3
+class County(models.Model):
  4
+    name = models.CharField(max_length=25)
  5
+    mpoly = models.MultiPolygonField(srid=4269) # Multipolygon in NAD83
  6
+    objects = models.GeoManager()
  7
+
  8
+class CountyFeat(models.Model):
  9
+    name = models.CharField(max_length=25)
  10
+    poly = models.PolygonField(srid=4269)
  11
+    objects = models.GeoManager()
  12
+
3 13
 class City(models.Model):
4 14
     name = models.CharField(max_length=25)
5 15
     population = models.IntegerField()
@@ -13,8 +23,16 @@ class Interstate(models.Model):
13 23
     length = models.DecimalField(max_digits=6, decimal_places=2)
14 24
     path = models.LineStringField()
15 25
     objects = models.GeoManager()
16  
-    
17  
-# Mapping dictionary for the City model.
  26
+
  27
+# Mapping dictionaries for the models above.
  28
+co_mapping = {'name' : 'Name',
  29
+              'mpoly' : 'MULTIPOLYGON', # Will convert POLYGON features into MULTIPOLYGONS.
  30
+              }
  31
+
  32
+cofeat_mapping = {'name' : 'Name',
  33
+                  'poly' : 'POLYGON',
  34
+                  }
  35
+
18 36
 city_mapping = {'name' : 'Name',
19 37
                 'population' : 'Population',
20 38
                 'density' : 'Density',
@@ -22,7 +40,6 @@ class Interstate(models.Model):
22 40
                 'point' : 'POINT',
23 41
                 }
24 42
 
25  
-# Mapping dictionary for the Interstate model.
26 43
 inter_mapping = {'name' : 'Name',
27 44
                  'length' : 'Length',
28 45
                  'path' : 'LINESTRING',
58  django/contrib/gis/tests/layermap/tests.py
@@ -2,12 +2,13 @@
2 2
 from copy import copy
3 3
 from datetime import date
4 4
 from decimal import Decimal
5  
-from models import City, Interstate, city_mapping, inter_mapping
  5
+from models import City, County, CountyFeat, Interstate, city_mapping, co_mapping, cofeat_mapping, inter_mapping
6 6
 from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal
7 7
 from django.contrib.gis.gdal import DataSource
8 8
 
9 9
 shp_path = os.path.dirname(__file__)
10 10
 city_shp = os.path.join(shp_path, 'cities/cities.shp')
  11
+co_shp = os.path.join(shp_path, 'counties/counties.shp')
11 12
 inter_shp = os.path.join(shp_path, 'interstates/interstates.shp')
12 13
 
13 14
 class LayerMapTest(unittest.TestCase):
@@ -110,6 +111,61 @@ def test03_layermap_strict(self):
110 111
                 self.assertAlmostEqual(p1[0], p2[0], 6)
111 112
                 self.assertAlmostEqual(p1[1], p2[1], 6)
112 113
 
  114
+    def test04_layermap_unique_multigeometry(self):
  115
+        "Testing the `unique`, and `transform` keywords and geometry collection conversion."
  116
+        # All the following should work.
  117
+        try:
  118
+            # Telling LayerMapping that we want no transformations performed on the data.
  119
+            lm = LayerMapping(County, co_shp, co_mapping, transform=False)
  120
+        
  121
+            # Specifying the source spatial reference system via the `source_srs` keyword.
  122
+            lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269)
  123
+            lm = LayerMapping(County, co_shp, co_mapping, source_srs='NAD83')
  124
+
  125
+            # Unique may take tuple or string parameters.
  126
+            for arg in ('name', ('name', 'mpoly')):
  127
+                lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
  128
+        except:
  129
+            self.fail('No exception should be raised for proper use of keywords.')
  130
+            
  131
+        # Testing invalid params for the `unique` keyword.
  132
+        for e, arg in ((TypeError, 5.0), (ValueError, 'foobar'), (ValueError, ('name', 'mpolygon'))):
  133
+            self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
  134
+
  135
+        # No source reference system defined in the shapefile, should raise an error.
  136
+        self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
  137
+
  138
+        # If a mapping is specified as a collection, all OGR fields that
  139
+        # are not collections will be converted into them.  For example,
  140
+        # a Point column would be converted to MultiPoint. Other things being done
  141
+        # w/the keyword args:
  142
+        #  `transform=False`: Specifies that no transform is to be done; this 
  143
+        #    has the effect of ignoring the spatial reference check (because the
  144
+        #    county shapefile does not have implicit spatial reference info).
  145
+        # 
  146
+        #  `unique='name'`: Creates models on the condition that they have 
  147
+        #    unique county names; geometries from each feature however will be
  148
+        #    appended to the geometry collection of the unique model.  Thus,
  149
+        #    all of the various islands in Honolulu county will be in in one
  150
+        #    database record with a MULTIPOLYGON type.
  151
+        lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True)
  152
+        lm.save()
  153
+
  154
+        # A reference that doesn't use the unique keyword; a new database record will
  155
+        # created for each polygon.
  156
+        lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False, silent=True)
  157
+        lm.save()
  158
+
  159
+        # Dictionary to hold what's expected in the shapefile.
  160
+        exp = {'names' : ('Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo'),
  161
+               'num' : (1, 2, 2, 19, 1), # Number of polygons for each.
  162
+               }
  163
+        for name, n in zip(exp['names'], exp['num']):
  164
+            c = County.objects.get(name=name) # Should only be one record.
  165
+            self.assertEqual(n, len(c.mpoly))
  166
+            qs = CountyFeat.objects.filter(name=name)
  167
+            self.assertEqual(n, qs.count())
  168
+            
113 169
 def suite():
114 170
     s = unittest.TestSuite()
115 171
     s.addTest(unittest.makeSuite(LayerMapTest))
80  django/contrib/gis/utils/layermapping.py
@@ -64,6 +64,13 @@
64 64
   transform:
65 65
    Setting this to False will disable all coordinate transformations.  
66 66
 
  67
+  unique:
  68
+   Setting this to the name, or a tuple of names, from the given
  69
+   model will create models unique only to the given name(s).
  70
+   Geometries will from each feature will be added into the collection
  71
+   associated with the unique model.  Forces transaction mode to
  72
+   be 'autocommit'.
  73
+
67 74
 Example:
68 75
 
69 76
  1. You need a GDAL-supported data source, like a shapefile.
@@ -182,7 +189,8 @@ class LayerMapping(object):
182 189
     def __init__(self, model, data, mapping, layer=0, 
183 190
                  source_srs=None, encoding=None, check=True, 
184 191
                  progress=False, interval=1000, strict=False, silent=False,
185  
-                 transaction_mode='commit_on_success', transform=True):
  192
+                 transaction_mode='commit_on_success', transform=True,
  193
+                 unique=False):
186 194
         "Takes the Django model, the data source, and the mapping (dictionary)"
187 195
 
188 196
         # Getting the field names and types from the model
@@ -208,8 +216,11 @@ def __init__(self, model, data, mapping, layer=0,
208 216
         # Checking the source spatial reference system, and getting
209 217
         # the coordinate transformation object (unless the `transform`
210 218
         # keyword is set to False)
211  
-        self.source_srs = self.check_srs(source_srs)
212  
-        self.transform = transform and self.coord_transform()
  219
+        if transform:
  220
+            self.source_srs = self.check_srs(source_srs)
  221
+            self.transform = self.coord_transform()
  222
+        else:
  223
+            self.transform = transform
213 224
 
214 225
         # Checking the layer -- intitialization of the object will fail if
215 226
         # things don't check out before hand.  This may be time-consuming,
@@ -232,6 +243,13 @@ def __init__(self, model, data, mapping, layer=0,
232 243
         else:
233 244
             self.encoding = None
234 245
 
  246
+        if unique:
  247
+            self.check_unique(unique)
  248
+            transaction_mode = 'autocommit' # Has to be set to autocommit.
  249
+            self.unique = unique
  250
+        else:
  251
+            self.unique = None
  252
+
235 253
         # Setting the transaction decorator with the function in the 
236 254
         # transaction modes dictionary.
237 255
         if transaction_mode in self.TRANSACTION_MODES:
@@ -314,6 +332,26 @@ def check_srs(self, source_srs):
314 332
         else:
315 333
             return sr
316 334
 
  335
+    def check_unique(self, unique):
  336
+        "Checks the `unique` keyword parameter -- may be a sequence or string."
  337
+        # Getting the geometry field; only the first encountered GeometryField
  338
+        # will be used.
  339
+        self.geom_field = False
  340
+        for model_field, ogr_fld in self.mapping.items():
  341
+            if ogr_fld in self.OGC_TYPES:
  342
+                self.geom_field = model_field
  343
+                break
  344
+
  345
+        if isinstance(unique, (list, tuple)):
  346
+            # List of fields to determine uniqueness with
  347
+            for attr in unique: 
  348
+                if not attr in self.mapping: raise ValueError
  349
+        elif isinstance(unique, basestring):
  350
+            # Only a single field passed in.
  351
+            if unique not in self.mapping: raise ValueError
  352
+        else:
  353
+            raise TypeError('Unique keyword argument must be set with a tuple, list, or string.')
  354
+
317 355
     def coord_transform(self):
318 356
         "Returns the coordinate transformation object."
319 357
         try:
@@ -371,6 +409,17 @@ def feature_kwargs(self, feat):
371 409
             
372 410
         return kwargs, all_prepped
373 411
 
  412
+    def unique_kwargs(self, kwargs):
  413
+        """
  414
+        Given the feature keyword arguments (from `feature_kwargs`) this routine
  415
+        will construct and return the uniqueness keyword arguments -- a subset
  416
+        of the feature kwargs.
  417
+        """
  418
+        if isinstance(self.unique, basestring):
  419
+            return {self.unique : kwargs[self.unique]}
  420
+        else:
  421
+            return dict((fld, kwargs[fld]) for fld in self.unique)
  422
+
374 423
     def verify_field(self, fld, model_field):
375 424
         """
376 425
         Verifies if the OGR Field contents are acceptable to the Django
@@ -485,8 +534,31 @@ def _save():
485 534
                 else:
486 535
                     # Constructing the model using the keyword args
487 536
                     if all_prepped:
488  
-                        m = self.model(**kwargs)
  537
+                        if self.unique:
  538
+                            # If we want unique models on a particular field, handle the
  539
+                            # geometry appropriately.
  540
+                            try:
  541
+                                # Getting the keyword arguments and retrieving
  542
+                                # the unique model.
  543
+                                u_kwargs = self.unique_kwargs(kwargs)
  544
+                                m = self.model.objects.get(**u_kwargs)
  545
+
  546
+                                # Getting the geometry (in OGR form), creating 
  547
+                                # one from the kwargs WKT, adding in additional 
  548
+                                # geometries, and update the attribute with the 
  549
+                                # just-updated geometry WKT.
  550
+                                geom = getattr(m, self.geom_field).ogr
  551
+                                new = OGRGeometry(kwargs[self.geom_field])
  552
+                                for g in new: geom.add(g) 
  553
+                                setattr(m, self.geom_field, geom.wkt)
  554
+                            except ObjectDoesNotExist:
  555
+                                # No unique model exists yet, create.
  556
+                                m = self.model(**kwargs)
  557
+                        else:
  558
+                            m = self.model(**kwargs)
  559
+
489 560
                         try:
  561
+                            # Attempting to save.
490 562
                             m.save()
491 563
                             num_saved += 1
492 564
                             if verbose: print 'Saved: %s' % m

0 notes on commit ef0f46f

Please sign in to comment.
Something went wrong with that request. Please try again.